diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 3037a3605e..018dfae1ae 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -52,4 +52,5 @@ jobs: with: os: ${{ matrix.os }} arch: ${{ matrix.arch }} - build-deps-only: ${{ inputs.build-deps-only || false }} \ No newline at end of file + build-deps-only: ${{ inputs.build-deps-only || false }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build_check_cache.yml b/.github/workflows/build_check_cache.yml index b4210fe8f2..cccc084944 100644 --- a/.github/workflows/build_check_cache.yml +++ b/.github/workflows/build_check_cache.yml @@ -54,4 +54,5 @@ jobs: valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' }} os: ${{ inputs.os }} arch: ${{ inputs.arch }} - build-deps-only: ${{ inputs.build-deps-only }} \ No newline at end of file + build-deps-only: ${{ inputs.build-deps-only }} + secrets: inherit diff --git a/.github/workflows/build_deps.yml b/.github/workflows/build_deps.yml index 88b89b25c5..e6a61029d5 100644 --- a/.github/workflows/build_deps.yml +++ b/.github/workflows/build_deps.yml @@ -127,4 +127,5 @@ jobs: cache-path: ${{ inputs.cache-path }} os: ${{ inputs.os }} arch: ${{ inputs.arch }} + secrets: inherit diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index c3fabeb9a8..8084e61ee5 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -157,7 +157,7 @@ jobs: path: ${{ github.workspace }}/build/OrcaSlicer*.exe - name: Upload artifacts Win PDB - if: matrix.os == 'windows-latest' + if: inputs.os == 'windows-latest' uses: actions/upload-artifact@v3 with: name: PDB diff --git a/.github/workflows/orca_bot.yml b/.github/workflows/orca_bot.yml index 67a7e6532f..da92304040 100644 --- a/.github/workflows/orca_bot.yml +++ b/.github/workflows/orca_bot.yml @@ -13,12 +13,12 @@ jobs: - uses: actions/stale@v5 with: days-before-issue-stale: 90 - days-before-issue-close: 14 + days-before-issue-close: 7 operations-per-run: 1000 stale-issue-label: "stale" ascending: true stale-issue-message: "GitHub bot: this issue is stale because it has been open for 90 days with no activity." - close-issue-message: "GitHub bot: This issue was closed because it has been inactive for 14 days since being marked as stale." + close-issue-message: "GitHub bot: This issue was closed because it has been inactive for 7 days since being marked as stale." days-before-pr-stale: -1 days-before-pr-close: -1 remove-issue-stale-when-updated: true diff --git a/deps/OpenSSL/OpenSSL.cmake b/deps/OpenSSL/OpenSSL.cmake index 7eb28af1a7..3dc103698e 100644 --- a/deps/OpenSSL/OpenSSL.cmake +++ b/deps/OpenSSL/OpenSSL.cmake @@ -19,7 +19,7 @@ if(WIN32) set(_install_cmd nmake install_sw ) else() if(APPLE) - set(_conf_cmd ./Configure ) + set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure ) else() set(_conf_cmd "./config") endif() diff --git a/localization/i18n/ko/OrcaSlicer_ko.po b/localization/i18n/ko/OrcaSlicer_ko.po index 5a423e8c99..ae654dc978 100644 --- a/localization/i18n/ko/OrcaSlicer_ko.po +++ b/localization/i18n/ko/OrcaSlicer_ko.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Orca Slicer\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-11-10 14:54+0800\n" -"PO-Revision-Date: 2023-11-14 11:26+0900\n" +"PO-Revision-Date: 2023-11-19 11:26+0900\n" "Last-Translator: Hotsolidinfill <138652683+Hotsolidinfill@users.noreply." "github.com>, crwusiz \n" "Language-Team: \n" @@ -857,16 +857,16 @@ msgid "Load..." msgstr "불러오기..." msgid "Orca Cube" -msgstr "Orca Cube" +msgstr "Orca 큐브" msgid "3DBenchy" -msgstr "3DBenchy" +msgstr "3D 벤치" msgid "Autodesk FDM Test" -msgstr "Autodesk FDM Test" +msgstr "Autodesk FDM 테스트" msgid "Voron Cube" -msgstr "Voron Cube" +msgstr "Voron 큐브" msgid "Cube" msgstr "정육면체" @@ -1355,7 +1355,7 @@ msgid "Infill density(%)" msgstr "내부 채움 밀도(%)" msgid "Auto Brim" -msgstr "자동 챙(브림)" +msgstr "자동 브림" msgid "Auto" msgstr "자동" @@ -1364,16 +1364,16 @@ msgid "Mouse ear" msgstr "생쥐 귀" msgid "Outer brim only" -msgstr "외부 챙만" +msgstr "외부 브림만" msgid "Inner brim only" -msgstr "내부 챙만" +msgstr "내부 브림만" msgid "Outer and inner brim" -msgstr "내부와 외부 챙" +msgstr "내부와 외부 브림" msgid "No-brim" -msgstr "챙 비활성화" +msgstr "브림 비활성화" msgid "Outer wall speed" msgstr "외벽 속도" @@ -1382,7 +1382,7 @@ msgid "Plate" msgstr "플레이트" msgid "Brim" -msgstr "챙(브림)" +msgstr "브림" msgid "Object/Part Setting" msgstr "개체/부품 설정" @@ -1952,10 +1952,10 @@ msgid "PA Profile" msgstr "PA 프로필" msgid "Factor K" -msgstr "Factor K" +msgstr "K 계수" msgid "Factor N" -msgstr "Factor N" +msgstr "N 계수" msgid "Setting Virtual slot information while printing is not supported" msgstr "출력 중에는 가상 슬롯 정보 설정이 지원되지 않습니다" @@ -2025,7 +2025,7 @@ msgid "" "factor K input box." msgstr "" "교정이 완료되었습니다. 당신의 고온 베드에서 아래 사진과 같이 가장 균일한 압" -"출 선을 찾아 왼쪽에 있는 값을 입력 상자의 Factor K에 채워주세요." +"출 선을 찾아 왼쪽에 있는 값을 입력 상자의 K 계수에 채워주세요." msgid "Save" msgstr "저장" @@ -2549,7 +2549,7 @@ msgid "Auto bed leveling" msgstr "자동 베드 레벨링" msgid "Heatbed preheating" -msgstr "고온 베드 예열" +msgstr "베드 예열" msgid "Sweeping XY mech mode" msgstr "스위핑 XY 기계 모드" @@ -2612,7 +2612,7 @@ msgid "Filament unloading" msgstr "필라멘트 빼는 중" msgid "Skip step pause" -msgstr "단계 일시정지 건너뛰기" +msgstr "단계 건너뛰기 일시중지" msgid "Filament loading" msgstr "필라멘트 넣는 중" @@ -2696,7 +2696,7 @@ msgid "" "automatically be set to 0℃." msgstr "" "챔버 온도를 40℃ 이하로 설정하면 챔버 온도 제어가 활성화되지 않습니다. 그리고 " -"목표 챔버 온도는 자동으로 0℃로 설정됩니다." +"목표 챔버 온도는 자동으로 0℃ 로 설정됩니다." msgid "Failed to start printing job" msgstr "출력 작업을 시작하지 못했습니다" @@ -2898,7 +2898,7 @@ msgid "Retract" msgstr "퇴출" msgid "Unretract" -msgstr "비퇴출(언리트렉트)" +msgstr "비퇴출" msgid "Filament Changes" msgstr "필라멘트 변경" @@ -3045,7 +3045,7 @@ msgid "Avoid extrusion calibration region" msgstr "압출 교정 영역을 피하십시오" msgid "Align to Y axis" -msgstr "Y축으로 정렬" +msgstr "Y축에 정렬" msgid "Add" msgstr "추가" @@ -3245,7 +3245,7 @@ msgid "will be closed before creating a new model. Do you want to continue?" msgstr "새 모델을 생성하기 전에 닫힙니다. 계속하시겠습니까?" msgid "Slice plate" -msgstr "슬라이스 플레이트" +msgstr "플레이트 슬라이스" msgid "Print plate" msgstr "플레이트 출력" @@ -3392,7 +3392,7 @@ msgid "Export all objects as STL" msgstr "모든 개체를 STL로 내보내기" msgid "Export Generic 3MF" -msgstr "Generic 3MF 내보내기" +msgstr "일반 3MF 내보내기" msgid "Export 3mf file without using some 3mf-extensions" msgstr "3mf-extensions을 사용하지 않고 3mf 파일 내보내기" @@ -3485,10 +3485,10 @@ msgid "Show object labels in 3D scene" msgstr "3D 화면에 개체 이름표 표시" msgid "Show &Overhang" -msgstr "돌출부 &보기" +msgstr "돌출부 보기" msgid "Show object overhang highlight in 3D scene" -msgstr "3D 장면에서 객체 오버행 하이라이트 표시" +msgstr "3D 장면에서 개체 오버행 하이라이트 표시" msgid "Preferences" msgstr "기본 설정" @@ -4018,7 +4018,7 @@ msgid "Silent" msgstr "조용한" msgid "Standard" -msgstr "스탠다드" +msgstr "표준" msgid "Sport" msgstr "스포츠" @@ -4510,7 +4510,7 @@ msgid "" "clogged when printing this filament in a closed enclosure. Please open the " "front door and/or remove the upper glass." msgstr "" -"현재의 고온 베드 온도가 상대적으로 높습니다. 닫힌 공간에서 이 필라멘트를 출력" +"현재 베드 온도가 상대적으로 높습니다. 닫힌 공간에서 이 필라멘트를 출력" "할 때 노즐이 막힐 수 있습니다. 전면 도어를 열거나 상단 유리를 제거하세요." msgid "" @@ -4690,13 +4690,13 @@ msgid "Message" msgstr "메시지" msgid "Reload from:" -msgstr "다음에서 다시 로드:" +msgstr "다음에서 새로고침:" msgid "Unable to reload:" -msgstr "다시 로드할 수 없음:" +msgstr "새로고침할 수 없음:" msgid "Error during reload" -msgstr "다시 로드 중 오류 발생" +msgstr "새로고침 중 오류가 발생했습니다." msgid "Slicing" msgstr "슬라이싱" @@ -5620,7 +5620,7 @@ msgid "" "Caution to use! Flow calibration on Textured PEI Plate may fail due to the " "scattered surface." msgstr "" -"사용상의 주의! Textured PEI 플레이트의 유량 교정은 표면이 분산되어 실패할 수 " +"사용상의 주의! 텍스처 PEI 플레이트의 유량 교정은 표면이 분산되어 실패할 수 " "있습니다." msgid "Automatic flow calibration using Micro Lidar" @@ -5874,7 +5874,7 @@ msgid "Set speed for external and internal bridges" msgstr "외부 및 내부 다리 속도 설정" msgid "Travel speed" -msgstr "이동 속도(트레블)" +msgstr "이동 속도" msgid "Acceleration" msgstr "가속도" @@ -5927,7 +5927,7 @@ msgid "Reserved keywords found" msgstr "예약어를 찾았습니다" msgid "Setting Overrides" -msgstr "설정 재정의(덮어쓰기)" +msgstr "설정 덮어쓰기" msgid "Retraction" msgstr "퇴출" @@ -6724,7 +6724,7 @@ msgid "A new Network plug-in(%s) available, Do you want to install it?" msgstr "새 네트워크 플러그인(%s)을 사용할 수 있습니다. 설치하시겠습니까?" msgid "New version of Orca Slicer" -msgstr "뱀부 스튜디오의 새 버전" +msgstr "Orca Slicer의 새 버전" msgid "Don't remind me of this version again" msgstr "이 버전을 다시 알리지 않음" @@ -6924,13 +6924,13 @@ msgid "Outer wall" msgstr "외벽" msgid "Overhang wall" -msgstr "돌출벽(오버행)" +msgstr "돌출벽" msgid "Sparse infill" msgstr "드문 내부 채움" msgid "Internal solid infill" -msgstr "내부 꽉찬 내부 채움" +msgstr "꽉찬 내부 채움" msgid "Top surface" msgstr "상단 표면" @@ -6972,7 +6972,7 @@ msgid "undefined error" msgstr "정의되지 않은 오류" msgid "too many files" -msgstr "너무 많은 파일" +msgstr "파일이 너무 많습니다" msgid "file too large" msgstr "파일이 너무 큽니다" @@ -6990,7 +6990,7 @@ msgid "failed finding central directory" msgstr "중앙 디렉토리를 찾지 못했습니다" msgid "not a ZIP archive" -msgstr "zip 아카이브가 아님" +msgstr "zip형식의 압축파일이 아님" msgid "invalid header or corrupted" msgstr "잘못된 헤더이거나 손상됨" @@ -7053,7 +7053,7 @@ msgid "file not found" msgstr "파일을 찾을 수 없습니다" msgid "archive too large" -msgstr "아카이브가 너무 큼" +msgstr "압축파일이 너무 큽니다" msgid "validation failed" msgstr "검증에 실패했습니다" @@ -7107,7 +7107,7 @@ msgid "" "Smooth mode of timelapse is not supported when \"by object\" sequence is " "enabled." msgstr "" -"타임랩스의 유연 모드는 \"개체별\" 출력순서가 ​​활성화된 경우 지원되지 않습니다." +"타임랩스의 유연 모드는 \"개체별\" 출력순서가 활성화된 경우 지원되지 않습니다." msgid "" "Please select \"By object\" print sequence to print multiple objects in " @@ -7238,7 +7238,7 @@ msgid "Plate %d: %s does not support filament %s" msgstr "%d: %s 플레이트는 %s 필라멘트를 지원하지 않습니다" msgid "Generating skirt & brim" -msgstr "스커트 & 챙(브림) 생성 중" +msgstr "스커트 & 브림 생성 중" msgid "Exporting G-code" msgstr "G코드 내보내는 중" @@ -7247,7 +7247,7 @@ msgid "Generating G-code" msgstr "G코드 생성 중" msgid "Failed processing of the filename_format template." -msgstr "파일 이름 형식(filename_format) 템플릿 처리에 실패했습니다." +msgstr "파일 이름 형식 템플릿 처리에 실패했습니다." msgid "Printable area" msgstr "출력 가능 영역" @@ -7394,7 +7394,7 @@ msgstr "벽 가로지름 방지" msgid "Detour and avoid to travel across wall which may cause blob on surface" msgstr "" -"벽을 가로질러 이동하지 않고 우회하여 표면에 방울(Blob) 발생을 방지합니다" +"벽을 가로질러 이동하지 않고 우회하여 표면에 방울 발생을 방지합니다" msgid "Avoid crossing wall - Max detour length" msgstr "벽 가로지름 방지 - 최대 우회 길이" @@ -7450,7 +7450,7 @@ msgid "Initial layer" msgstr "초기 레이어" msgid "Initial layer bed temperature" -msgstr "초기 레이어 베드(Bed) 온도" +msgstr "초기 레이어 베드 온도" msgid "" "Bed temperature of the initial layer. Value 0 means the filament does not " @@ -7734,51 +7734,51 @@ msgstr "" "로 계산됩니다. 기본값은 150%입니다." msgid "Brim width" -msgstr "챙(브림) 너비" +msgstr "브림 너비" msgid "Distance from model to the outermost brim line" -msgstr "모델과 가장 바깥쪽 챙(브림) 선까지의 거리" +msgstr "모델과 가장 바깥쪽 브림 선까지의 거리" msgid "Brim type" -msgstr "챙(브림) 유형" +msgstr "브림 유형" msgid "" "This controls the generation of the brim at outer and/or inner side of " "models. Auto means the brim width is analysed and calculated automatically." msgstr "" -"모델의 외부 그리고/또는 내부에서 챙(브림)의 생성을 제어합니다. 자동은 챙(브" -"림) 너비가 자동으로 분석 및 계산됨을 의미합니다." +"모델의 외부 그리고/또는 내부에서 브림의 생성을 제어합니다. 자동은 브림" +"너비가 자동으로 분석 및 계산됨을 의미합니다." msgid "Brim-object gap" -msgstr "챙(브림)-개체 간격" +msgstr "브림-개체 간격" msgid "" "A gap between innermost brim line and object can make brim be removed more " "easily" msgstr "" -"가장 안쪽 챙(브림) 라인과 개체 사이에 간격을 주어 쉽게 챙(브림)을 제거 할 수 " +"가장 안쪽 브림 라인과 개체 사이에 간격을 주어 쉽게 브림을 제거 할 수 " "있게 합니다" msgid "Brim ears" -msgstr "챙(브림) 귀" +msgstr "브림 귀" msgid "Only draw brim over the sharp edges of the model." -msgstr "모델의 날카로운 가장자리에만 챙을 그립니다." +msgstr "모델의 날카로운 가장자리에만 브림을 그립니다." msgid "Brim ear max angle" -msgstr "챙(브림) 귀 최대 각도" +msgstr "브림 귀 최대 각도" msgid "" "Maximum angle to let a brim ear appear. \n" "If set to 0, no brim will be created. \n" "If set to ~180, brim will be created on everything but straight sections." msgstr "" -"챙 귀가 나타날 수 있는 최대 각도.\n" -"0으로 설정하면 챙이 생성되지 않습니다.\n" -"~180으로 설정하면 직선 부분을 제외한 모든 부분에 챙이 생성됩니다." +"브림 귀가 나타날 수 있는 최대 각도.\n" +"0으로 설정하면 브림이 생성되지 않습니다.\n" +"~180으로 설정하면 직선 부분을 제외한 모든 부분에 브림이 생성됩니다." msgid "Brim ear detection radius" -msgstr "챙 귀 감지 반경" +msgstr "브림 귀 감지 반경" msgid "" "The geometry will be decimated before dectecting sharp angles. This " @@ -7912,13 +7912,13 @@ msgstr "" "로 설정하고 다리에 지지대를 생성하지 않으려면 매우 큰 값으로 설정합니다." msgid "End G-code" -msgstr "End G-code" +msgstr "종료 G코드" msgid "End G-code when finish the whole printing" -msgstr "전체 출력이 끝날때의 End G-code" +msgstr "전체 출력이 끝날때의 종료 G코드" msgid "End G-code when finish the printing of this filament" -msgstr "이 필라멘트의 출력이 끝날때의 End G-code" +msgstr "이 필라멘트의 출력이 끝날때의 종료 G코드" msgid "Ensure vertical shell thickness" msgstr "수직 쉘 두께 확보" @@ -8335,7 +8335,7 @@ msgstr "가용성 재료" msgid "" "Soluble material is commonly used to print support and support interface" msgstr "" -"가용성 재료는 일반적으로 지지대 및 지지대 접점(Interface)을 출력하는 데 사용" +"가용성 재료는 일반적으로 지지대 및 지지대 접점을 출력하는 데 사용" "됩니다" msgid "Support material" @@ -8344,7 +8344,7 @@ msgstr "지지대 재료" msgid "" "Support material is commonly used to print support and support interface" msgstr "" -"지원 재료는 일반적으로 지지대 및 지지대 접점(Interface)을 출력하는 데 사용됩" +"지원 재료는 일반적으로 지지대 및 지지대 접점을 출력하는 데 사용됩" "니다" msgid "Softening temperature" @@ -8451,10 +8451,10 @@ msgstr "" "정합니다." msgid "0 (no open anchors)" -msgstr "0(개방형 고정점 없음)" +msgstr "0 (개방형 고정점 없음)" msgid "1000 (unlimited)" -msgstr "1000(무제한)" +msgstr "1000 (무제한)" msgid "Maximum length of the infill anchor" msgstr "내부 채움 고정점 최대 길이" @@ -8480,7 +8480,7 @@ msgstr "" "결과가 생성됩니다." msgid "0 (Simple connect)" -msgstr "0(단순 연결)" +msgstr "0 (단순 연결)" msgid "Acceleration of outer walls" msgstr "외벽의 가속도" @@ -8630,7 +8630,7 @@ msgstr "" "\"close_fan_the_first_x_layers\" + 1 에서 최대 허용 속도로 가도됩니다." msgid "Support interface fan speed" -msgstr "지지대 접점(Interface) 팬 속도" +msgstr "지지대 접점 팬 속도" msgid "" "This fan speed is enforced during all support interfaces, to be able to " @@ -8733,16 +8733,16 @@ msgstr "" "수 있는지를 결정합니다" msgid "Undefine" -msgstr "Undefine" +msgstr "알수없음" msgid "Hardened steel" -msgstr "Hardened steel" +msgstr "경화강 노즐" msgid "Stainless steel" -msgstr "Stainless steel" +msgstr "스테인레스강 노즐" msgid "Brass" -msgstr "Brass" +msgstr "동 노즐" msgid "Nozzle HRC" msgstr "노즐 록웰 경도(HRC)" @@ -8835,7 +8835,7 @@ msgid "The printer cost per hour" msgstr "시간당 프린터 비용" msgid "money/h" -msgstr "원/h" +msgstr "비용/시간" msgid "Support control chamber temperature" msgstr "챔버 온도 제어 지원" @@ -8858,7 +8858,7 @@ msgstr "" "G코드 명령: M106 P3 S(0-255)" msgid "G-code flavor" -msgstr "G코드 호환(Flavor)" +msgstr "G코드 유형" msgid "What kind of gcode the printer is compatible with" msgstr "프린터와 호환되는 G코드 종류" @@ -9180,7 +9180,7 @@ msgstr "" "도/더 작은 너비) 압출로 또는 그 반대로 출력할 때 발생하는 급격한 압출 속도 변" "화를 완화합니다.\n" "\n" -"이는 압출된 체적 유량(mm3/sec)이 시간에 따라 변할 수 있는 최대 속도를 정의합" +"이는 압출된 체적 유량(mm3/초)이 시간에 따라 변할 수 있는 최대 속도를 정의합" "니다. 값이 높을수록 더 높은 압출 속도 변경이 허용되어 속도 전환이 더 빨라진다" "는 의미입니다.\n" "\n" @@ -9190,10 +9190,10 @@ msgstr "" "값이 필요하지 않습니다. 그러나 기능 속도가 크게 달라지는 특정 경우에는 약간" "의 이점을 제공할 수 있습니다. 예를 들어 돌출부로 인해 급격하게 감속이 발생하" "는 경우입니다. 이러한 경우 약 300-350mm3/s2의 높은 값이 권장됩니다. 이렇게 하" -"면 프레셔 어드밴스(Pressure advance)가 더 부드러운 유량 전환을 달성하는 데 도" +"면 프레셔 어드밴스가 더 부드러운 유량 전환을 달성하는 데 도" "움이 될 만큼 충분히 매끄러워질 수 있기 때문입니다.\n" "\n" -"프레셔 어드밴스(Pressure advance) 기능이 없는 느린 프린터의 경우 값을 훨씬 낮" +"프레셔 어드밴스 기능이 없는 느린 프린터의 경우 값을 훨씬 낮" "게 설정해야 합니다. 10-15mm3/s2 값은 직접 구동 압출기의 좋은 시작점이고 보우" "덴 스타일의 경우 5-10mm3/s2입니다.\n" "\n" @@ -9401,7 +9401,7 @@ msgid "mm²" msgstr "mm²" msgid "Detect overhang wall" -msgstr "돌출벽(오버행 벽) 감지" +msgstr "돌출벽 감지" #, c-format, boost-format msgid "" @@ -9499,7 +9499,7 @@ msgid "" msgstr "" "퇴출 길이에 비례한 닦기 전 퇴출량(닦기를 시작하기 전에 일부 필라멘트를 퇴출시" "키면 노즐 끝에 녹아있는 소량의 필라멘트만 남게되어 추가 누수 위험이 줄어들 " -"수 있습니다_by MMT)" +"수 있습니다)" msgid "Retract when change layer" msgstr "레이어 변경 시 퇴출" @@ -9597,7 +9597,7 @@ msgid "" "When the retraction is compensated after changing tool, the extruder will " "push this additional amount of filament." msgstr "" -"툴(Tool) 교체 후 퇴출이 보정되면 압출기가 보정된 양 만큼의 추가 필라멘트를 밀" +"툴 교체 후 퇴출이 보정되면 압출기가 보정된 양 만큼의 추가 필라멘트를 밀" "어냅니다." msgid "Retraction Speed" @@ -9607,7 +9607,7 @@ msgid "Speed of retractions" msgstr "퇴출 속도" msgid "Deretraction Speed" -msgstr "퇴출 복귀(디-리트렉션) 속도" +msgstr "퇴출 복귀 속도" msgid "" "Speed for reloading filament into extruder. Zero means same speed with " @@ -9705,10 +9705,10 @@ msgstr "" "은 80%입니다" msgid "Skirt distance" -msgstr "스커트-챙(브림)/개체 거리" +msgstr "스커트 거리" msgid "Distance from skirt to brim or object" -msgstr "스커트와 챙(브림) 또는 개체와의 거리" +msgstr "스커트와 브림 또는 개체와의 거리" msgid "Skirt height" msgstr "스커트 높이" @@ -9763,7 +9763,7 @@ msgid "" "generated model has no seam" msgstr "" "나선화는 외부 윤곽선의 z 이동을 부드럽게 합니다. 그리고 개체를 꽉찬 하단 레이" -"어(Solid bottom layers)가 있는 단일 벽으로 출력합니다. 최종 생성된 출력물에" +"어가 있는 단일 벽으로 출력합니다. 최종 생성된 출력물에" "는 솔기가 없습니다" msgid "" @@ -9907,7 +9907,7 @@ msgid "" "normal(manual) or tree(manual) is selected, only support enforcers are " "generated" msgstr "" -"일반(자동) 및 나무(자동)는 지지대를 자동으로 생성합니다. 일반(수동) 또는 나무" +"일반(자동) 및 나무(자동)은 지지대를 자동으로 생성합니다. 일반(수동) 또는 나무" "(수동) 선택시에는 강제된 지지대만 생성됩니다" msgid "normal(auto)" @@ -10175,18 +10175,18 @@ msgstr "" "으로 계산됩니다 " msgid "Auto brim width" -msgstr "나무 지지대 자동 챙(브림) 너비" +msgstr "나무 지지대 자동 브림 너비" msgid "" "Enabling this option means the width of the brim for tree support will be " "automatically calculated" -msgstr "옵션을 활성화하면 나무 지지대의 챙(브림) 너비가 자동으로 계산됩니다" +msgstr "옵션을 활성화하면 나무 지지대의 브림 너비가 자동으로 계산됩니다" msgid "Tree support brim width" -msgstr "나무 지지대 챙(브림) 너비" +msgstr "나무 지지대 브림 너비" msgid "Distance from tree branch to the outermost brim line" -msgstr "나무 지지대 가지에서 가장 바깥쪽 챙(브림)까지의 거리" +msgstr "나무 지지대 가지에서 가장 바깥쪽 브림까지의 거리" msgid "Tip Diameter" msgstr "끝 직경" @@ -10527,7 +10527,7 @@ msgid "Rotate the polyhole every layer." msgstr "레이어마다 폴리홀을 회전시킵니다." msgid "G-code thumbnails" -msgstr "G코드 미리보기(썸네일)" +msgstr "G코드 미리보기" msgid "" "Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the " @@ -10778,13 +10778,13 @@ msgid "Export Settings" msgstr "설정 내보내기" msgid "Export settings to a file." -msgstr "설정을 파일로 내보냅니다." +msgstr "설정을 파일로 내보내기." msgid "Send progress to pipe" -msgstr "Send progress to pipe" +msgstr "진행 상황을 파이프로 보내기" msgid "Send progress to pipe." -msgstr "Send progress to pipe." +msgstr "진행 상황을 파이프로 보내기." msgid "Arrange Options" msgstr "정렬 옵션" @@ -10799,7 +10799,7 @@ msgid "Repetions count of the whole model" msgstr "전체 모델의 반복 횟수" msgid "Ensure on bed" -msgstr "Ensure on bed" +msgstr "베드에서 확인" msgid "" "Lift the object above the bed when it is partially below. Disabled by default" @@ -11645,10 +11645,10 @@ msgid "PA Calibration" msgstr "PA 교정" msgid "DDE" -msgstr "다이렉트 드라이브(DDE)" +msgstr "다이렉트" msgid "Bowden" -msgstr "보우덴(Bowden)" +msgstr "보우덴" msgid "Extruder type" msgstr "압출기 타입" @@ -12064,8 +12064,8 @@ msgid "" "Did you know that when printing models have a small contact interface with " "the printing surface, it's recommended to use a brim?" msgstr "" -"더 나은 안착을 위한 챙(브림)\n" -"출력 모델이 베드 표면과의 접촉면이 작을 때 챙(브림)를 사용하는 것이 좋다는 사" +"더 나은 안착을 위한 브림\n" +"출력 모델이 베드 표면과의 접촉면이 작을 때 브림를 사용하는 것이 좋다는 사" "실을 알고 있습니까?" #: resources/data/hints.ini: [hint:Set parameters for multiple objects] diff --git a/resources/images/copy_menu.svg b/resources/images/copy_menu.svg new file mode 100644 index 0000000000..23e0bfeb2a --- /dev/null +++ b/resources/images/copy_menu.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/copy_menu_dark.svg b/resources/images/copy_menu_dark.svg new file mode 100644 index 0000000000..eaee113a1b --- /dev/null +++ b/resources/images/copy_menu_dark.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/toolbar_measure.svg b/resources/images/toolbar_measure.svg new file mode 100644 index 0000000000..1606b5ee6f --- /dev/null +++ b/resources/images/toolbar_measure.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + diff --git a/resources/images/toolbar_measure_dark.svg b/resources/images/toolbar_measure_dark.svg new file mode 100644 index 0000000000..a273e75347 --- /dev/null +++ b/resources/images/toolbar_measure_dark.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + diff --git a/resources/shaders/110/background.fs b/resources/shaders/110/background.fs new file mode 100644 index 0000000000..b148440898 --- /dev/null +++ b/resources/shaders/110/background.fs @@ -0,0 +1,11 @@ +#version 110 + +uniform vec4 top_color; +uniform vec4 bottom_color; + +varying vec2 tex_coord; + +void main() +{ + gl_FragColor = mix(bottom_color, top_color, tex_coord.y); +} diff --git a/resources/shaders/110/background.vs b/resources/shaders/110/background.vs new file mode 100644 index 0000000000..9b56ab43a2 --- /dev/null +++ b/resources/shaders/110/background.vs @@ -0,0 +1,12 @@ +#version 110 + +attribute vec3 v_position; +attribute vec2 v_tex_coord; + +varying vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = vec4(v_position, 1.0); +} diff --git a/resources/shaders/cali.fs b/resources/shaders/110/flat.fs similarity index 100% rename from resources/shaders/cali.fs rename to resources/shaders/110/flat.fs diff --git a/resources/shaders/110/flat.vs b/resources/shaders/110/flat.vs new file mode 100644 index 0000000000..d9063f0c70 --- /dev/null +++ b/resources/shaders/110/flat.vs @@ -0,0 +1,11 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; + +void main() +{ + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/110/flat_clip.fs b/resources/shaders/110/flat_clip.fs new file mode 100644 index 0000000000..ececb8eb1a --- /dev/null +++ b/resources/shaders/110/flat_clip.fs @@ -0,0 +1,15 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform vec4 uniform_color; + +varying vec3 clipping_planes_dots; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + gl_FragColor = uniform_color; +} diff --git a/resources/shaders/110/flat_clip.vs b/resources/shaders/110/flat_clip.vs new file mode 100644 index 0000000000..cdf7d4b3b2 --- /dev/null +++ b/resources/shaders/110/flat_clip.vs @@ -0,0 +1,23 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat4 volume_world_matrix; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +attribute vec3 v_position; + +varying vec3 clipping_planes_dots; + +void main() +{ + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + vec4 world_pos = volume_world_matrix * vec4(v_position, 1.0); + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/110/flat_texture.fs b/resources/shaders/110/flat_texture.fs new file mode 100644 index 0000000000..ffe193b1c0 --- /dev/null +++ b/resources/shaders/110/flat_texture.fs @@ -0,0 +1,10 @@ +#version 110 + +uniform sampler2D uniform_texture; + +varying vec2 tex_coord; + +void main() +{ + gl_FragColor = texture2D(uniform_texture, tex_coord); +} diff --git a/resources/shaders/110/flat_texture.vs b/resources/shaders/110/flat_texture.vs new file mode 100644 index 0000000000..dc4868b04d --- /dev/null +++ b/resources/shaders/110/flat_texture.vs @@ -0,0 +1,15 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; +attribute vec2 v_tex_coord; + +varying vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/110/gouraud.fs b/resources/shaders/110/gouraud.fs new file mode 100644 index 0000000000..6f354ff9a6 --- /dev/null +++ b/resources/shaders/110/gouraud.fs @@ -0,0 +1,107 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +//BBS: add grey and orange +//const vec3 GREY = vec3(0.9, 0.9, 0.9); +const vec3 ORANGE = vec3(0.8, 0.4, 0.0); +const vec3 LightRed = vec3(0.78, 0.0, 0.0); +const vec3 LightBlue = vec3(0.73, 1.0, 1.0); +const float EPSILON = 0.0001; + +struct PrintVolumeDetection +{ + // 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid + int type; + // type = 0 (rectangle): + // x = min.x, y = min.y, z = max.x, w = max.y + // type = 1 (circle): + // x = center.x, y = center.y, z = radius + vec4 xy_data; + // x = min z, y = max z + vec2 z_data; +}; + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform vec4 uniform_color; +uniform bool use_color_clip_plane; +uniform vec4 uniform_color_clip_plane_1; +uniform vec4 uniform_color_clip_plane_2; +uniform SlopeDetection slope; + +//BBS: add outline_color +uniform bool is_outline; + +#ifdef ENABLE_ENVIRONMENT_MAP + uniform sampler2D environment_tex; + uniform bool use_environment_tex; +#endif // ENABLE_ENVIRONMENT_MAP + +uniform PrintVolumeDetection print_volume; + +varying vec3 clipping_planes_dots; +varying float color_clip_plane_dot; + +// x = diffuse, y = specular; +varying vec2 intensity; + +varying vec4 world_pos; +varying float world_normal_z; +varying vec3 eye_normal; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + vec4 color; + if (use_color_clip_plane) { + color.rgb = (color_clip_plane_dot < 0.0) ? uniform_color_clip_plane_1.rgb : uniform_color_clip_plane_2.rgb; + color.a = uniform_color.a; + } + else + color = uniform_color; + + if (slope.actived) { + if(world_pos.z<0.1&&world_pos.z>-0.1) + { + color.rgb = LightBlue; + color.a = 0.8; + } + else if( world_normal_z < slope.normal_z - EPSILON) + { + color.rgb = color.rgb * 0.5 + LightRed * 0.5; + color.a = 0.8; + } + } + // if the fragment is outside the print volume -> use darker color + vec3 pv_check_min = ZERO; + vec3 pv_check_max = ZERO; + if (print_volume.type == 0) { + // rectangle + pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x); + pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y); + } + else if (print_volume.type == 1) { + // circle + float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy); + pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x); + pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y); + } + color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb; + + //BBS: add outline_color + if (is_outline) + gl_FragColor = uniform_color; +#ifdef ENABLE_ENVIRONMENT_MAP + else if (use_environment_tex) + gl_FragColor = vec4(0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x, color.a); +#endif + else + gl_FragColor = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a); +} \ No newline at end of file diff --git a/resources/shaders/110/gouraud.vs b/resources/shaders/110/gouraud.vs new file mode 100644 index 0000000000..ff3a190c79 --- /dev/null +++ b/resources/shaders/110/gouraud.vs @@ -0,0 +1,81 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SHININESS 5.0 + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; +uniform SlopeDetection slope; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; +// Color clip plane - general orientation. Used by the cut gizmo. +uniform vec4 color_clip_plane; + +attribute vec3 v_position; +attribute vec3 v_normal; + +// x = diffuse, y = specular; +varying vec2 intensity; + +varying vec3 clipping_planes_dots; +varying float color_clip_plane_dot; + +varying vec4 world_pos; +varying float world_normal_z; +varying vec3 eye_normal; + +void main() +{ + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + // Point in homogenous coordinates. + world_pos = volume_world_matrix * vec4(v_position, 1.0); + + // z component of normal vector in world coordinate used for slope shading + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0; + + gl_Position = projection_matrix * position; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + color_clip_plane_dot = dot(world_pos, color_clip_plane); +} diff --git a/resources/shaders/110/gouraud_light.fs b/resources/shaders/110/gouraud_light.fs new file mode 100644 index 0000000000..970185a00e --- /dev/null +++ b/resources/shaders/110/gouraud_light.fs @@ -0,0 +1,12 @@ +#version 110 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/110/gouraud_light.vs b/resources/shaders/110/gouraud_light.vs new file mode 100644 index 0000000000..135a23b3da --- /dev/null +++ b/resources/shaders/110/gouraud_light.vs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; + +attribute vec3 v_position; +attribute vec3 v_normal; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * position; +} diff --git a/resources/shaders/110/gouraud_light_instanced.fs b/resources/shaders/110/gouraud_light_instanced.fs new file mode 100644 index 0000000000..970185a00e --- /dev/null +++ b/resources/shaders/110/gouraud_light_instanced.fs @@ -0,0 +1,12 @@ +#version 110 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/110/gouraud_light_instanced.vs b/resources/shaders/110/gouraud_light_instanced.vs new file mode 100644 index 0000000000..f512a9cafc --- /dev/null +++ b/resources/shaders/110/gouraud_light_instanced.vs @@ -0,0 +1,50 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; + +// vertex attributes +attribute vec3 v_position; +attribute vec3 v_normal; +// instance attributes +attribute vec3 i_offset; +attribute vec2 i_scales; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 world_position = vec4(v_position * vec3(vec2(1.5 * i_scales.x), 1.5 * i_scales.y) + i_offset - vec3(0.0, 0.0, 0.5 * i_scales.y), 1.0); + vec4 eye_position = view_model_matrix * world_position; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * eye_position; +} diff --git a/resources/shaders/110/imgui.fs b/resources/shaders/110/imgui.fs new file mode 100644 index 0000000000..4b0e27ce9d --- /dev/null +++ b/resources/shaders/110/imgui.fs @@ -0,0 +1,11 @@ +#version 110 + +uniform sampler2D Texture; + +varying vec2 Frag_UV; +varying vec4 Frag_Color; + +void main() +{ + gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st); +} \ No newline at end of file diff --git a/resources/shaders/110/imgui.vs b/resources/shaders/110/imgui.vs new file mode 100644 index 0000000000..100813e2b5 --- /dev/null +++ b/resources/shaders/110/imgui.vs @@ -0,0 +1,17 @@ +#version 110 + +uniform mat4 ProjMtx; + +attribute vec2 Position; +attribute vec2 UV; +attribute vec4 Color; + +varying vec2 Frag_UV; +varying vec4 Frag_Color; + +void main() +{ + Frag_UV = UV; + Frag_Color = Color; + gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0); +} \ No newline at end of file diff --git a/resources/shaders/options_110.fs b/resources/shaders/110/mm_contour.fs similarity index 100% rename from resources/shaders/options_110.fs rename to resources/shaders/110/mm_contour.fs diff --git a/resources/shaders/110/mm_contour.vs b/resources/shaders/110/mm_contour.vs new file mode 100644 index 0000000000..b37394b619 --- /dev/null +++ b/resources/shaders/110/mm_contour.vs @@ -0,0 +1,15 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform float offset; + +attribute vec3 v_position; + +void main() +{ + // Add small epsilon to z to solve z-fighting between painted triangles and contour lines. + vec4 clip_position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); + clip_position.z -= offset * abs(clip_position.w); + gl_Position = clip_position; +} diff --git a/resources/shaders/110/mm_gouraud.fs b/resources/shaders/110/mm_gouraud.fs new file mode 100644 index 0000000000..8ca23df66d --- /dev/null +++ b/resources/shaders/110/mm_gouraud.fs @@ -0,0 +1,90 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const float EPSILON = 0.0001; +//BBS: add grey and orange +//const vec3 GREY = vec3(0.9, 0.9, 0.9); +const vec3 ORANGE = vec3(0.8, 0.4, 0.0); +const vec3 LightRed = vec3(0.78, 0.0, 0.0); +const vec3 LightBlue = vec3(0.73, 1.0, 1.0); +uniform vec4 uniform_color; + +uniform bool volume_mirrored; + +uniform mat4 view_model_matrix; +uniform mat3 view_normal_matrix; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; +varying vec4 world_pos; + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; +uniform SlopeDetection slope; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + vec3 color = uniform_color.rgb; + float alpha = uniform_color.a; + + vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); +#ifdef FLIP_TRIANGLE_NORMALS + triangle_normal = -triangle_normal; +#endif + + if (volume_mirrored) + triangle_normal = -triangle_normal; + + vec3 transformed_normal = normalize(slope.volume_world_normal_matrix * triangle_normal); + + if (slope.actived) { + if(world_pos.z<0.1&&world_pos.z>-0.1) + { + color = LightBlue; + alpha = 1.0; + } + else if( transformed_normal.z < slope.normal_z - EPSILON) + { + color = color * 0.5 + LightRed * 0.5; + alpha = 1.0; + } + } + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(view_normal_matrix * triangle_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + // x = diffuse, y = specular; + vec2 intensity = vec2(0.0); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (view_model_matrix * model_pos).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); +} diff --git a/resources/shaders/110/mm_gouraud.vs b/resources/shaders/110/mm_gouraud.vs new file mode 100644 index 0000000000..e8c679b6bf --- /dev/null +++ b/resources/shaders/110/mm_gouraud.vs @@ -0,0 +1,35 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +uniform mat4 volume_world_matrix; +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +attribute vec3 v_position; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; +varying vec4 world_pos; +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; +uniform SlopeDetection slope; +void main() +{ + model_pos = vec4(v_position, 1.0); + // Point in homogenous coordinates. + world_pos = volume_world_matrix * model_pos; + + gl_Position = projection_matrix * view_model_matrix * model_pos; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} diff --git a/resources/shaders/110/printbed.fs b/resources/shaders/110/printbed.fs new file mode 100644 index 0000000000..833dff08f4 --- /dev/null +++ b/resources/shaders/110/printbed.fs @@ -0,0 +1,34 @@ +#version 110 + +const vec3 back_color_dark = vec3(0.235, 0.235, 0.235); +const vec3 back_color_light = vec3(0.365, 0.365, 0.365); + +uniform sampler2D texture; +uniform bool transparent_background; +uniform bool svg_source; + +varying vec2 tex_coord; + +vec4 svg_color() +{ + // takes foreground from texture + vec4 fore_color = texture2D(texture, tex_coord); + + // calculates radial gradient + vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coord.xy) - vec2(0.5))))); + + // blends foreground with background + return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); +} + +vec4 non_svg_color() +{ + // takes foreground from texture + vec4 color = texture2D(texture, tex_coord); + return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a); +} + +void main() +{ + gl_FragColor = svg_source ? svg_color() : non_svg_color(); +} \ No newline at end of file diff --git a/resources/shaders/110/printbed.vs b/resources/shaders/110/printbed.vs new file mode 100644 index 0000000000..dc4868b04d --- /dev/null +++ b/resources/shaders/110/printbed.vs @@ -0,0 +1,15 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; +attribute vec2 v_tex_coord; + +varying vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/110/thumbnail.fs b/resources/shaders/110/thumbnail.fs new file mode 100644 index 0000000000..4b269734ee --- /dev/null +++ b/resources/shaders/110/thumbnail.fs @@ -0,0 +1,16 @@ +#version 110 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +varying vec2 intensity; +//varying float drop; +varying vec4 world_pos; + +void main() +{ + if (world_pos.z < 0.0) + discard; + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/110/thumbnail.vs b/resources/shaders/110/thumbnail.vs new file mode 100644 index 0000000000..69fd0c753c --- /dev/null +++ b/resources/shaders/110/thumbnail.vs @@ -0,0 +1,50 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; + +attribute vec3 v_position; +attribute vec3 v_normal; + +// x = tainted, y = specular; +varying vec2 intensity; +varying vec4 world_pos; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + // Point in homogenous coordinates. + world_pos = volume_world_matrix * vec4(v_position, 1.0); + + gl_Position = projection_matrix * position; +} diff --git a/resources/shaders/110/variable_layer_height.fs b/resources/shaders/110/variable_layer_height.fs new file mode 100644 index 0000000000..693c1c6a0b --- /dev/null +++ b/resources/shaders/110/variable_layer_height.fs @@ -0,0 +1,41 @@ +#version 110 + +#define M_PI 3.1415926535897932384626433832795 + +// 2D texture (1D texture split by the rows) of color along the object Z axis. +uniform sampler2D z_texture; +// Scaling from the Z texture rows coordinate to the normalized texture row coordinate. +uniform float z_to_texture_row; +uniform float z_texture_row_to_normalized; +uniform float z_cursor; +uniform float z_cursor_band_width; + +// x = tainted, y = specular; +varying vec2 intensity; + +varying float object_z; + +void main() +{ + float object_z_row = z_to_texture_row * object_z; + // Index of the row in the texture. + float z_texture_row = floor(object_z_row); + // Normalized coordinate from 0. to 1. + float z_texture_col = object_z_row - z_texture_row; + float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; + // Calculate level of detail from the object Z coordinate. + // This makes the slowly sloping surfaces to be shown with high detail (with stripes), + // and the vertical surfaces to be shown with low detail (no stripes) + float z_in_cells = object_z_row * 190.; + // Gradient of Z projected on the screen. + float dx_vtc = dFdx(z_in_cells); + float dy_vtc = dFdy(z_in_cells); + float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.); + // Sample the Z texture. Texture coordinates are normalized to <0, 1>. + vec4 color = vec4(0.25, 0.25, 0.25, 1.0); + if (z_texture_row >= 0.0) + color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), + texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); + // Mix the final color. + gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); +} diff --git a/resources/shaders/110/variable_layer_height.vs b/resources/shaders/110/variable_layer_height.vs new file mode 100644 index 0000000000..2d6334f44e --- /dev/null +++ b/resources/shaders/110/variable_layer_height.vs @@ -0,0 +1,60 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SHININESS 5.0 + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; +uniform float object_max_z; + +attribute vec3 v_position; +attribute vec3 v_normal; +attribute vec2 v_tex_coord; + +// x = tainted, y = specular; +varying vec2 intensity; + +varying float object_z; + +void main() +{ + // ===================================================== + // NOTE: + // when object_max_z > 0.0 we are rendering the overlay + // when object_max_z == 0.0 we are rendering the volumes + // ===================================================== + + // First transform the normal into camera space and normalize the result. + vec3 normal = (object_max_z > 0.0) ? vec3(0.0, 0.0, 1.0) : normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular) + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + // Scaled to widths of the Z texture. + object_z = (object_max_z > 0.0) ? object_max_z * v_tex_coord.y : (volume_world_matrix * vec4(v_position, 1.0)).z; + + gl_Position = projection_matrix * position; +} diff --git a/resources/shaders/140/background.fs b/resources/shaders/140/background.fs new file mode 100644 index 0000000000..c21f3a70cd --- /dev/null +++ b/resources/shaders/140/background.fs @@ -0,0 +1,11 @@ +#version 140 + +uniform vec4 top_color; +uniform vec4 bottom_color; + +in vec2 tex_coord; + +void main() +{ + gl_FragColor = mix(bottom_color, top_color, tex_coord.y); +} diff --git a/resources/shaders/140/background.vs b/resources/shaders/140/background.vs new file mode 100644 index 0000000000..13609b3a21 --- /dev/null +++ b/resources/shaders/140/background.vs @@ -0,0 +1,12 @@ +#version 140 + +in vec3 v_position; +in vec2 v_tex_coord; + +out vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/flat.fs b/resources/shaders/140/flat.fs new file mode 100644 index 0000000000..e74124dcae --- /dev/null +++ b/resources/shaders/140/flat.fs @@ -0,0 +1,8 @@ +#version 140 + +uniform vec4 uniform_color; + +void main() +{ + gl_FragColor = uniform_color; +} diff --git a/resources/shaders/140/flat.vs b/resources/shaders/140/flat.vs new file mode 100644 index 0000000000..7042671de2 --- /dev/null +++ b/resources/shaders/140/flat.vs @@ -0,0 +1,11 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +in vec3 v_position; + +void main() +{ + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/flat_clip.fs b/resources/shaders/140/flat_clip.fs new file mode 100644 index 0000000000..b77e0bfaa6 --- /dev/null +++ b/resources/shaders/140/flat_clip.fs @@ -0,0 +1,17 @@ +#version 140 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform vec4 uniform_color; + +in vec3 clipping_planes_dots; + +out vec4 out_color; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + out_color = uniform_color; +} diff --git a/resources/shaders/140/flat_clip.vs b/resources/shaders/140/flat_clip.vs new file mode 100644 index 0000000000..40cddf1e5a --- /dev/null +++ b/resources/shaders/140/flat_clip.vs @@ -0,0 +1,23 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat4 volume_world_matrix; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +in vec3 v_position; + +out vec3 clipping_planes_dots; + +void main() +{ + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + vec4 world_pos = volume_world_matrix * vec4(v_position, 1.0); + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/flat_texture.fs b/resources/shaders/140/flat_texture.fs new file mode 100644 index 0000000000..dec946721e --- /dev/null +++ b/resources/shaders/140/flat_texture.fs @@ -0,0 +1,10 @@ +#version 140 + +uniform sampler2D uniform_texture; + +in vec2 tex_coord; + +void main() +{ + gl_FragColor = texture(uniform_texture, tex_coord); +} diff --git a/resources/shaders/140/flat_texture.vs b/resources/shaders/140/flat_texture.vs new file mode 100644 index 0000000000..57d8ca3b7c --- /dev/null +++ b/resources/shaders/140/flat_texture.vs @@ -0,0 +1,15 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +in vec3 v_position; +in vec2 v_tex_coord; + +out vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/gouraud.fs b/resources/shaders/140/gouraud.fs new file mode 100644 index 0000000000..84bce5c035 --- /dev/null +++ b/resources/shaders/140/gouraud.fs @@ -0,0 +1,109 @@ +#version 140 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +//BBS: add grey and orange +//const vec3 GREY = vec3(0.9, 0.9, 0.9); +const vec3 ORANGE = vec3(0.8, 0.4, 0.0); +const vec3 LightRed = vec3(0.78, 0.0, 0.0); +const vec3 LightBlue = vec3(0.73, 1.0, 1.0); +const float EPSILON = 0.0001; + +struct PrintVolumeDetection +{ + // 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid + int type; + // type = 0 (rectangle): + // x = min.x, y = min.y, z = max.x, w = max.y + // type = 1 (circle): + // x = center.x, y = center.y, z = radius + vec4 xy_data; + // x = min z, y = max z + vec2 z_data; +}; + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform vec4 uniform_color; +uniform bool use_color_clip_plane; +uniform vec4 uniform_color_clip_plane_1; +uniform vec4 uniform_color_clip_plane_2; +uniform SlopeDetection slope; + +//BBS: add outline_color +uniform bool is_outline; + +#ifdef ENABLE_ENVIRONMENT_MAP + uniform sampler2D environment_tex; + uniform bool use_environment_tex; +#endif // ENABLE_ENVIRONMENT_MAP + +uniform PrintVolumeDetection print_volume; + +in vec3 clipping_planes_dots; +in float color_clip_plane_dot; + +// x = diffuse, y = specular; +in vec2 intensity; + +in vec4 world_pos; +in float world_normal_z; +in vec3 eye_normal; + +out vec4 out_color; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + vec4 color; + if (use_color_clip_plane) { + color.rgb = (color_clip_plane_dot < 0.0) ? uniform_color_clip_plane_1.rgb : uniform_color_clip_plane_2.rgb; + color.a = uniform_color.a; + } + else + color = uniform_color; + + if (slope.actived) { + if(world_pos.z<0.1&&world_pos.z>-0.1) + { + color.rgb = LightBlue; + color.a = 0.8; + } + else if( world_normal_z < slope.normal_z - EPSILON) + { + color.rgb = color.rgb * 0.5 + LightRed * 0.5; + color.a = 0.8; + } + } + // if the fragment is outside the print volume -> use darker color + vec3 pv_check_min = ZERO; + vec3 pv_check_max = ZERO; + if (print_volume.type == 0) { + // rectangle + pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x); + pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y); + } + else if (print_volume.type == 1) { + // circle + float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy); + pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x); + pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y); + } + color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb; + + //BBS: add outline_color + if (is_outline) + out_color = uniform_color; +#ifdef ENABLE_ENVIRONMENT_MAP + else if (use_environment_tex) + out_color = vec4(0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color.rgb * intensity.x, color.a); +#endif + else + out_color = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a); +} \ No newline at end of file diff --git a/resources/shaders/140/gouraud.vs b/resources/shaders/140/gouraud.vs new file mode 100644 index 0000000000..72dd4ff1bf --- /dev/null +++ b/resources/shaders/140/gouraud.vs @@ -0,0 +1,81 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SHININESS 5.0 + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; +uniform SlopeDetection slope; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; +// Color clip plane - general orientation. Used by the cut gizmo. +uniform vec4 color_clip_plane; + +in vec3 v_position; +in vec3 v_normal; + +// x = diffuse, y = specular; +out vec2 intensity; + +out vec3 clipping_planes_dots; +out float color_clip_plane_dot; + +out vec4 world_pos; +out float world_normal_z; +out vec3 eye_normal; + +void main() +{ + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + // Point in homogenous coordinates. + world_pos = volume_world_matrix * vec4(v_position, 1.0); + + // z component of normal vector in world coordinate used for slope shading + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0; + + gl_Position = projection_matrix * position; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + color_clip_plane_dot = dot(world_pos, color_clip_plane); +} diff --git a/resources/shaders/140/gouraud_light.fs b/resources/shaders/140/gouraud_light.fs new file mode 100644 index 0000000000..de616e066a --- /dev/null +++ b/resources/shaders/140/gouraud_light.fs @@ -0,0 +1,12 @@ +#version 140 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +in vec2 intensity; + +void main() +{ + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/140/gouraud_light.vs b/resources/shaders/140/gouraud_light.vs new file mode 100644 index 0000000000..fad848f8bd --- /dev/null +++ b/resources/shaders/140/gouraud_light.vs @@ -0,0 +1,45 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; + +in vec3 v_position; +in vec3 v_normal; + +// x = tainted, y = specular; +out vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * position; +} diff --git a/resources/shaders/140/gouraud_light_instanced.fs b/resources/shaders/140/gouraud_light_instanced.fs new file mode 100644 index 0000000000..de616e066a --- /dev/null +++ b/resources/shaders/140/gouraud_light_instanced.fs @@ -0,0 +1,12 @@ +#version 140 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +in vec2 intensity; + +void main() +{ + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/140/gouraud_light_instanced.vs b/resources/shaders/140/gouraud_light_instanced.vs new file mode 100644 index 0000000000..e0437ca397 --- /dev/null +++ b/resources/shaders/140/gouraud_light_instanced.vs @@ -0,0 +1,50 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; + +// vertex attributes +in vec3 v_position; +in vec3 v_normal; +// instance attributes +in vec3 i_offset; +in vec2 i_scales; + +// x = tainted, y = specular; +out vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 world_position = vec4(v_position * vec3(vec2(1.5 * i_scales.x), 1.5 * i_scales.y) + i_offset - vec3(0.0, 0.0, 0.5 * i_scales.y), 1.0); + vec4 eye_position = view_model_matrix * world_position; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = projection_matrix * eye_position; +} diff --git a/resources/shaders/140/imgui.fs b/resources/shaders/140/imgui.fs new file mode 100644 index 0000000000..4b2571749f --- /dev/null +++ b/resources/shaders/140/imgui.fs @@ -0,0 +1,11 @@ +#version 140 + +uniform sampler2D Texture; + +in vec2 Frag_UV; +in vec4 Frag_Color; + +void main() +{ + gl_FragColor = Frag_Color * texture(Texture, Frag_UV.st); +} \ No newline at end of file diff --git a/resources/shaders/140/imgui.vs b/resources/shaders/140/imgui.vs new file mode 100644 index 0000000000..fd743bdf2d --- /dev/null +++ b/resources/shaders/140/imgui.vs @@ -0,0 +1,17 @@ +#version 140 + +uniform mat4 ProjMtx; + +in vec2 Position; +in vec2 UV; +in vec4 Color; + +out vec2 Frag_UV; +out vec4 Frag_Color; + +void main() +{ + Frag_UV = UV; + Frag_Color = Color; + gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0); +} \ No newline at end of file diff --git a/resources/shaders/140/mm_contour.fs b/resources/shaders/140/mm_contour.fs new file mode 100644 index 0000000000..e74124dcae --- /dev/null +++ b/resources/shaders/140/mm_contour.fs @@ -0,0 +1,8 @@ +#version 140 + +uniform vec4 uniform_color; + +void main() +{ + gl_FragColor = uniform_color; +} diff --git a/resources/shaders/140/mm_contour.vs b/resources/shaders/140/mm_contour.vs new file mode 100644 index 0000000000..679291ba6d --- /dev/null +++ b/resources/shaders/140/mm_contour.vs @@ -0,0 +1,15 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform float offset; + +in vec3 v_position; + +void main() +{ + // Add small epsilon to z to solve z-fighting between painted triangles and contour lines. + vec4 clip_position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); + clip_position.z -= offset * abs(clip_position.w); + gl_Position = clip_position; +} diff --git a/resources/shaders/140/mm_gouraud.fs b/resources/shaders/140/mm_gouraud.fs new file mode 100644 index 0000000000..2156394bea --- /dev/null +++ b/resources/shaders/140/mm_gouraud.fs @@ -0,0 +1,90 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const float EPSILON = 0.0001; +//BBS: add grey and orange +//const vec3 GREY = vec3(0.9, 0.9, 0.9); +const vec3 ORANGE = vec3(0.8, 0.4, 0.0); +const vec3 LightRed = vec3(0.78, 0.0, 0.0); +const vec3 LightBlue = vec3(0.73, 1.0, 1.0); +uniform vec4 uniform_color; + +uniform bool volume_mirrored; + +uniform mat4 view_model_matrix; +uniform mat3 view_normal_matrix; + +in vec3 clipping_planes_dots; +in vec4 model_pos; +in vec4 world_pos; + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; +uniform SlopeDetection slope; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + vec3 color = uniform_color.rgb; + float alpha = uniform_color.a; + + vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); +#ifdef FLIP_TRIANGLE_NORMALS + triangle_normal = -triangle_normal; +#endif + + if (volume_mirrored) + triangle_normal = -triangle_normal; + + vec3 transformed_normal = normalize(slope.volume_world_normal_matrix * triangle_normal); + + if (slope.actived) { + if(world_pos.z<0.1&&world_pos.z>-0.1) + { + color = LightBlue; + alpha = 1.0; + } + else if( transformed_normal.z < slope.normal_z - EPSILON) + { + color = color * 0.5 + LightRed * 0.5; + alpha = 1.0; + } + } + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(view_normal_matrix * triangle_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + // x = diffuse, y = specular; + vec2 intensity = vec2(0.0); + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (view_model_matrix * model_pos).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); +} diff --git a/resources/shaders/140/mm_gouraud.vs b/resources/shaders/140/mm_gouraud.vs new file mode 100644 index 0000000000..4add5c0aee --- /dev/null +++ b/resources/shaders/140/mm_gouraud.vs @@ -0,0 +1,35 @@ +#version 140 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +uniform mat4 volume_world_matrix; +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +in vec3 v_position; + +out vec3 clipping_planes_dots; +out vec4 model_pos; +out vec4 world_pos; +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; +uniform SlopeDetection slope; +void main() +{ + model_pos = vec4(v_position, 1.0); + // Point in homogenous coordinates. + world_pos = volume_world_matrix * model_pos; + + gl_Position = projection_matrix * view_model_matrix * model_pos; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} diff --git a/resources/shaders/140/printbed.fs b/resources/shaders/140/printbed.fs new file mode 100644 index 0000000000..6d927a749c --- /dev/null +++ b/resources/shaders/140/printbed.fs @@ -0,0 +1,35 @@ +#version 140 + +const vec3 back_color_dark = vec3(0.235, 0.235, 0.235); +const vec3 back_color_light = vec3(0.365, 0.365, 0.365); + +uniform sampler2D in_texture; +uniform bool transparent_background; +uniform bool svg_source; + +in vec2 tex_coord; +out vec4 frag_color; + +vec4 svg_color() +{ + // takes foreground from texture + vec4 fore_color = texture(in_texture, tex_coord); + + // calculates radial gradient + vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coord.xy) - vec2(0.5))))); + + // blends foreground with background + return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); +} + +vec4 non_svg_color() +{ + // takes foreground from texture + vec4 color = texture(in_texture, tex_coord); + return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a); +} + +void main() +{ + frag_color = svg_source ? svg_color() : non_svg_color(); +} \ No newline at end of file diff --git a/resources/shaders/140/printbed.vs b/resources/shaders/140/printbed.vs new file mode 100644 index 0000000000..57d8ca3b7c --- /dev/null +++ b/resources/shaders/140/printbed.vs @@ -0,0 +1,15 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +in vec3 v_position; +in vec2 v_tex_coord; + +out vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/thumbnail.fs b/resources/shaders/140/thumbnail.fs new file mode 100644 index 0000000000..9e6d5d854c --- /dev/null +++ b/resources/shaders/140/thumbnail.fs @@ -0,0 +1,16 @@ +#version 140 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +in vec2 intensity; +//varying float drop; +in vec4 world_pos; + +void main() +{ + if (world_pos.z < 0.0) + discard; + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/140/thumbnail.vs b/resources/shaders/140/thumbnail.vs new file mode 100644 index 0000000000..90ca720a37 --- /dev/null +++ b/resources/shaders/140/thumbnail.vs @@ -0,0 +1,50 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; + +in vec3 v_position; +in vec3 v_normal; + +// x = tainted, y = specular; +out vec2 intensity; +out vec4 world_pos; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + // Point in homogenous coordinates. + world_pos = volume_world_matrix * vec4(v_position, 1.0); + + gl_Position = projection_matrix * position; +} diff --git a/resources/shaders/140/variable_layer_height.fs b/resources/shaders/140/variable_layer_height.fs new file mode 100644 index 0000000000..cf1fc309cc --- /dev/null +++ b/resources/shaders/140/variable_layer_height.fs @@ -0,0 +1,41 @@ +#version 140 + +#define M_PI 3.1415926535897932384626433832795 + +// 2D texture (1D texture split by the rows) of color along the object Z axis. +uniform sampler2D z_texture; +// Scaling from the Z texture rows coordinate to the normalized texture row coordinate. +uniform float z_to_texture_row; +uniform float z_texture_row_to_normalized; +uniform float z_cursor; +uniform float z_cursor_band_width; + +// x = tainted, y = specular; +in vec2 intensity; + +in float object_z; + +void main() +{ + float object_z_row = z_to_texture_row * object_z; + // Index of the row in the texture. + float z_texture_row = floor(object_z_row); + // Normalized coordinate from 0. to 1. + float z_texture_col = object_z_row - z_texture_row; + float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; + // Calculate level of detail from the object Z coordinate. + // This makes the slowly sloping surfaces to be shown with high detail (with stripes), + // and the vertical surfaces to be shown with low detail (no stripes) + float z_in_cells = object_z_row * 190.; + // Gradient of Z projected on the screen. + float dx_vtc = dFdx(z_in_cells); + float dy_vtc = dFdy(z_in_cells); + float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.); + // Sample the Z texture. Texture coordinates are normalized to <0, 1>. + vec4 color = vec4(0.25, 0.25, 0.25, 1.0); + if (z_texture_row >= 0.0) + color = mix(texture(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), + texture(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); + // Mix the final color. + gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); +} diff --git a/resources/shaders/140/variable_layer_height.vs b/resources/shaders/140/variable_layer_height.vs new file mode 100644 index 0000000000..d8deb2f9ee --- /dev/null +++ b/resources/shaders/140/variable_layer_height.vs @@ -0,0 +1,60 @@ +#version 140 + +#define INTENSITY_CORRECTION 0.6 + +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SHININESS 5.0 + +#define INTENSITY_AMBIENT 0.3 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; +uniform mat4 volume_world_matrix; +uniform float object_max_z; + +in vec3 v_position; +in vec3 v_normal; +in vec2 v_tex_coord; + +// x = tainted, y = specular; +out vec2 intensity; + +out float object_z; + +void main() +{ + // ===================================================== + // NOTE: + // when object_max_z > 0.0 we are rendering the overlay + // when object_max_z == 0.0 we are rendering the volumes + // ===================================================== + + // First transform the normal into camera space and normalize the result. + vec3 normal = (object_max_z > 0.0) ? vec3(0.0, 0.0, 1.0) : normalize(view_normal_matrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular) + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + // Scaled to widths of the Z texture. + object_z = (object_max_z > 0.0) ? object_max_z * v_tex_coord.y : (volume_world_matrix * vec4(v_position, 1.0)).z; + + gl_Position = projection_matrix * position; +} diff --git a/resources/shaders/background.fs b/resources/shaders/background.fs new file mode 100644 index 0000000000..b148440898 --- /dev/null +++ b/resources/shaders/background.fs @@ -0,0 +1,11 @@ +#version 110 + +uniform vec4 top_color; +uniform vec4 bottom_color; + +varying vec2 tex_coord; + +void main() +{ + gl_FragColor = mix(bottom_color, top_color, tex_coord.y); +} diff --git a/resources/shaders/background.vs b/resources/shaders/background.vs new file mode 100644 index 0000000000..b7c1d92c0e --- /dev/null +++ b/resources/shaders/background.vs @@ -0,0 +1,9 @@ +#version 110 + +varying vec2 tex_coord; + +void main() +{ + gl_Position = gl_Vertex; + tex_coord = gl_MultiTexCoord0.xy; +} diff --git a/resources/shaders/flat.fs b/resources/shaders/flat.fs new file mode 100644 index 0000000000..ab656998df --- /dev/null +++ b/resources/shaders/flat.fs @@ -0,0 +1,8 @@ +#version 110 + +uniform vec4 uniform_color; + +void main() +{ + gl_FragColor = uniform_color; +} diff --git a/resources/shaders/cali.vs b/resources/shaders/flat.vs similarity index 100% rename from resources/shaders/cali.vs rename to resources/shaders/flat.vs diff --git a/resources/shaders/flat_texture.fs b/resources/shaders/flat_texture.fs new file mode 100644 index 0000000000..ffe193b1c0 --- /dev/null +++ b/resources/shaders/flat_texture.fs @@ -0,0 +1,10 @@ +#version 110 + +uniform sampler2D uniform_texture; + +varying vec2 tex_coord; + +void main() +{ + gl_FragColor = texture2D(uniform_texture, tex_coord); +} diff --git a/resources/shaders/flat_texture.vs b/resources/shaders/flat_texture.vs new file mode 100644 index 0000000000..27addc7526 --- /dev/null +++ b/resources/shaders/flat_texture.vs @@ -0,0 +1,9 @@ +#version 110 + +varying vec2 tex_coord; + +void main() +{ + gl_Position = ftransform(); + tex_coord = gl_MultiTexCoord0.xy; +} diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 3f12a6e92c..4084efdbf1 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -48,7 +48,6 @@ varying vec2 intensity; uniform PrintVolumeDetection print_volume; -varying vec4 model_pos; varying vec4 world_pos; varying float world_normal_z; varying vec3 eye_normal; diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index 79d7a63c07..c8b3d7b335 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -38,7 +38,6 @@ varying vec2 intensity; varying vec3 clipping_planes_dots; -varying vec4 model_pos; varying vec4 world_pos; varying float world_normal_z; varying vec3 eye_normal; @@ -60,7 +59,6 @@ void main() NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - model_pos = gl_Vertex; // Point in homogenous coordinates. world_pos = volume_world_matrix * gl_Vertex; diff --git a/resources/shaders/options_110.vs b/resources/shaders/options_110.vs deleted file mode 100644 index 5f2ab23504..0000000000 --- a/resources/shaders/options_110.vs +++ /dev/null @@ -1,22 +0,0 @@ -#version 110 - -uniform bool use_fixed_screen_size; -uniform float zoom; -uniform float point_size; -uniform float near_plane_height; - -float fixed_screen_size() -{ - return point_size; -} - -float fixed_world_size() -{ - return (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w; -} - -void main() -{ - gl_Position = ftransform(); - gl_PointSize = use_fixed_screen_size ? fixed_screen_size() : fixed_world_size(); -} diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs deleted file mode 100644 index e9b61304f2..0000000000 --- a/resources/shaders/options_120.fs +++ /dev/null @@ -1,22 +0,0 @@ -// version 120 is needed for gl_PointCoord -#version 120 - -uniform vec4 uniform_color; -uniform float percent_outline_radius; -uniform float percent_center_radius; - -vec4 calc_color(float radius, vec4 color) -{ - return ((radius < percent_center_radius) || (radius > 1.0 - percent_outline_radius)) ? - vec4(0.5 * color.rgb, color.a) : color; -} - -void main() -{ - vec2 pos = (gl_PointCoord - 0.5) * 2.0; - float radius = length(pos); - if (radius > 1.0) - discard; - - gl_FragColor = calc_color(radius, uniform_color); -} diff --git a/resources/shaders/options_120.vs b/resources/shaders/options_120.vs deleted file mode 100644 index edb503fb2b..0000000000 --- a/resources/shaders/options_120.vs +++ /dev/null @@ -1,22 +0,0 @@ -#version 120 - -uniform bool use_fixed_screen_size; -uniform float zoom; -uniform float point_size; -uniform float near_plane_height; - -float fixed_screen_size() -{ - return point_size; -} - -float fixed_world_size() -{ - return (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w; -} - -void main() -{ - gl_Position = ftransform(); - gl_PointSize = use_fixed_screen_size ? fixed_screen_size() : fixed_world_size(); -} diff --git a/resources/shaders/printbed.fs b/resources/shaders/printbed.fs index d1316ca2fe..833dff08f4 100644 --- a/resources/shaders/printbed.fs +++ b/resources/shaders/printbed.fs @@ -1,21 +1,21 @@ #version 110 -const vec3 back_color_dark = vec3(0.235, 0.235, 0.235); +const vec3 back_color_dark = vec3(0.235, 0.235, 0.235); const vec3 back_color_light = vec3(0.365, 0.365, 0.365); uniform sampler2D texture; uniform bool transparent_background; uniform bool svg_source; -varying vec2 tex_coords; +varying vec2 tex_coord; vec4 svg_color() { // takes foreground from texture - vec4 fore_color = texture2D(texture, tex_coords); + vec4 fore_color = texture2D(texture, tex_coord); // calculates radial gradient - vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coords.xy) - vec2(0.5))))); + vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coord.xy) - vec2(0.5))))); // blends foreground with background return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); @@ -24,7 +24,7 @@ vec4 svg_color() vec4 non_svg_color() { // takes foreground from texture - vec4 color = texture2D(texture, tex_coords); + vec4 color = texture2D(texture, tex_coord); return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a); } diff --git a/resources/shaders/printbed.vs b/resources/shaders/printbed.vs index 7633017f12..27addc7526 100644 --- a/resources/shaders/printbed.vs +++ b/resources/shaders/printbed.vs @@ -1,14 +1,9 @@ #version 110 -attribute vec3 v_position; -attribute vec2 v_tex_coords; - -varying vec2 tex_coords; +varying vec2 tex_coord; void main() { - gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position.x, v_position.y, v_position.z, 1.0); - // the following line leads to crash on some Intel graphics card - //gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position, 1.0); - tex_coords = v_tex_coords; + gl_Position = ftransform(); + tex_coord = gl_MultiTexCoord0.xy; } diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 3ab0342d2c..7271af2d97 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -1553,23 +1553,11 @@ int CLI::run(int argc, char **argv) o->cut(Z, m_config.opt_float("cut"), &out); } #else - ModelObject* object = model.objects.front(); - const BoundingBoxf3& box = object->bounding_box(); - const float Margin = 20.0; - const float max_x = box.size()(0) / 2.0 + Margin; - const float min_x = -max_x; - const float max_y = box.size()(1) / 2.0 + Margin; - const float min_y = -max_y; - - std::array plane_points; - plane_points[0] = { min_x, min_y, 0 }; - plane_points[1] = { max_x, min_y, 0 }; - plane_points[2] = { max_x, max_y, 0 }; - plane_points[3] = { min_x, max_y, 0 }; - for (Vec3d& point : plane_points) { - point += box.center(); - } - model.objects.front()->cut(0, plane_points, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower); + Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()), + ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); + auto cut_objects = cut.perform_with_plane(); + for (ModelObject* obj : cut_objects) + model.add_object(*obj); #endif model.delete_object(size_t(0)); } @@ -2279,12 +2267,12 @@ int CLI::run(int argc, char **argv) else colors.push_back("#FFFFFF"); - std::vector> colors_out(colors.size()); - unsigned char rgb_color[3] = {}; + std::vector colors_out(colors.size()); + ColorRGBA rgb_color; for (const std::string& color : colors) { - Slic3r::GUI::BitmapCache::parse_color(color, rgb_color); + Slic3r::decode_color(color, rgb_color); size_t color_idx = &color - &colors.front(); - colors_out[color_idx] = { float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f }; + colors_out[color_idx] = rgb_color; } int gl_major, gl_minor, gl_verbos; @@ -2353,19 +2341,16 @@ int CLI::run(int argc, char **argv) // continue; for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { const ModelInstance &model_instance = *model_object.instances[instance_idx]; - glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, "volume", true, false, true); + glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, false, true); //glvolume_collection.volumes.back()->geometry_id = key.geometry_id; std::string color = filament_color?filament_color->get_at(extruder_id - 1):"#00FF00"; - unsigned char rgb_color[3] = {}; - Slic3r::GUI::BitmapCache::parse_color(color, rgb_color); - glvolume_collection.volumes.back()->set_render_color( float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f); + ColorRGBA rgb_color; + Slic3r::decode_color(color, rgb_color); + glvolume_collection.volumes.back()->set_render_color(rgb_color); - std::array new_color; - new_color[0] = float(rgb_color[0]) / 255.f; - new_color[1] = float(rgb_color[1]) / 255.f; - new_color[2] = float(rgb_color[2]) / 255.f; - new_color[3] = 1.f; + ColorRGBA new_color; + new_color = rgb_color; glvolume_collection.volumes.back()->set_color(new_color); glvolume_collection.volumes.back()->printable = model_instance.printable; } diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 1774eb28ac..9380ea1649 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -161,6 +161,7 @@ namespace ImGui const wchar_t ClippyMarker = 0x0802; const wchar_t InfoMarker = 0x0803; const wchar_t SliderFloatEditBtnIcon = 0x0804; + const wchar_t ClipboardBtnIcon = 0x0805; // BBS const wchar_t CircleButtonIcon = 0x0810; @@ -196,6 +197,7 @@ namespace ImGui const wchar_t CloseBlockNotifButton = 0x0833; const wchar_t CloseBlockNotifHoverButton = 0x0834; const wchar_t BlockNotifErrorIcon = 0x0835; + const wchar_t ClipboardBtnDarkIcon = 0x0836; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/imgui/imgui.cpp b/src/imgui/imgui.cpp index fd32e4cf5f..8f45263a39 100644 --- a/src/imgui/imgui.cpp +++ b/src/imgui/imgui.cpp @@ -6046,8 +6046,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->Pos = FindBestWindowPosForPopup(window); else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) window->Pos = FindBestWindowPosForPopup(window); - else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) - window->Pos = FindBestWindowPosForPopup(window); + // Orca: Allow fixed tooltip pos while still being clamped inside the render area + else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_is_child_tooltip) { + if (window_pos_set_by_api) { + // Hack: add ImGuiWindowFlags_Popup so it does not follow cursor + ImGuiWindowFlags old_flags = window->Flags; + window->Flags |= ImGuiWindowFlags_Popup; + window->Pos = FindBestWindowPosForPopup(window); + window->Flags = old_flags; + } else { + window->Pos = FindBestWindowPosForPopup(window); + } + } // Calculate the range of allowed position for that window (to be movable and visible past safe area padding) // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect. diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index 4ab007f8b8..0df41f1be5 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -93,6 +93,9 @@ public: // Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices. bool all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& bbox, bool ignore_bottom = true) const; + const std::pair, std::vector>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; } + const std::pair, std::vector>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; } + private: // Source definition of the print bed geometry (PrintConfig::printable_area) std::vector m_bed_shape; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3cfb82b073..0134707178 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -56,6 +56,8 @@ set(lisbslic3r_sources Clipper2Utils.cpp Clipper2Utils.hpp ClipperZUtils.hpp + Color.cpp + Color.hpp Config.cpp Config.hpp CurveAnalyzer.cpp @@ -200,12 +202,17 @@ set(lisbslic3r_sources BlacklistedLibraryCheck.hpp LocalesUtils.cpp LocalesUtils.hpp + CutUtils.cpp + CutUtils.hpp Model.cpp Model.hpp ModelArrange.hpp ModelArrange.cpp MultiMaterialSegmentation.cpp MultiMaterialSegmentation.hpp + Measure.hpp + Measure.cpp + MeasureUtils.hpp CustomGCode.cpp CustomGCode.hpp Arrange.hpp @@ -305,6 +312,7 @@ set(lisbslic3r_sources Surface.hpp SurfaceCollection.cpp SurfaceCollection.hpp + SurfaceMesh.hpp SVG.cpp SVG.hpp Technologies.hpp diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp new file mode 100644 index 0000000000..c282e307ed --- /dev/null +++ b/src/libslic3r/Color.cpp @@ -0,0 +1,420 @@ +#include "libslic3r.h" +#include "Color.hpp" + +#include + +static const float INV_255 = 1.0f / 255.0f; + +namespace Slic3r { + +// Conversion from RGB to HSV color space +// The input RGB values are in the range [0, 1] +// The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v) +{ + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); + + const float max_comp = std::max(std::max(r, g), b); + const float min_comp = std::min(std::min(r, g), b); + const float delta = max_comp - min_comp; + + if (delta > 0.0f) { + if (max_comp == r) + h = 60.0f * (std::fmod(((g - b) / delta), 6.0f)); + else if (max_comp == g) + h = 60.0f * (((b - r) / delta) + 2.0f); + else if (max_comp == b) + h = 60.0f * (((r - g) / delta) + 4.0f); + + s = (max_comp > 0.0f) ? delta / max_comp : 0.0f; + } + else { + h = 0.0f; + s = 0.0f; + } + v = max_comp; + + while (h < 0.0f) { h += 360.0f; } + while (h > 360.0f) { h -= 360.0f; } + + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); +} + +// Conversion from HSV to RGB color space +// The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +// The output RGB values are in the range [0, 1] +static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b) +{ + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); + + const float chroma = v * s; + const float h_prime = std::fmod(h / 60.0f, 6.0f); + const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f)); + const float m = v - chroma; + + if (0.0f <= h_prime && h_prime < 1.0f) { + r = chroma; + g = x; + b = 0.0f; + } + else if (1.0f <= h_prime && h_prime < 2.0f) { + r = x; + g = chroma; + b = 0.0f; + } + else if (2.0f <= h_prime && h_prime < 3.0f) { + r = 0.0f; + g = chroma; + b = x; + } + else if (3.0f <= h_prime && h_prime < 4.0f) { + r = 0.0f; + g = x; + b = chroma; + } + else if (4.0f <= h_prime && h_prime < 5.0f) { + r = x; + g = 0.0f; + b = chroma; + } + else if (5.0f <= h_prime && h_prime < 6.0f) { + r = chroma; + g = 0.0f; + b = x; + } + else { + r = 0.0f; + g = 0.0f; + b = 0.0f; + } + + r += m; + g += m; + b += m; + + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); +} + +class Randomizer +{ + std::random_device m_rd; + +public: + float random_float(float min, float max) { + std::mt19937 rand_generator(m_rd()); + std::uniform_real_distribution distrib(min, max); + return distrib(rand_generator); + } +}; + +ColorRGB::ColorRGB(float r, float g, float b) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }) +{ +} + +ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) }) +{ +} + +bool ColorRGB::operator < (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + else if (m_data[i] > other.m_data[i]) + return false; + } + + return false; +} + +bool ColorRGB::operator > (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + else if (m_data[i] < other.m_data[i]) + return false; + } + + return false; +} + +ColorRGB ColorRGB::operator + (const ColorRGB& other) const +{ + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGB ColorRGB::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA::ColorRGBA(float r, float g, float b, float a) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) }) +{ +} + +ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) }) +{ +} + +bool ColorRGBA::operator < (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + else if (m_data[i] > other.m_data[i]) + return false; + } + + return false; +} + +bool ColorRGBA::operator > (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + else if (m_data[i] < other.m_data[i]) + return false; + } + + return false; +} + +ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const +{ + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA ColorRGBA::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + ret.m_data[3] = this->m_data[3]; + return ret; +} + +ColorRGB operator * (float value, const ColorRGB& other) { return other * value; } +ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; } + +ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGB complementary(const ColorRGB& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() }; +} + +ColorRGBA complementary(const ColorRGBA& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() }; +} + +ColorRGB saturate(const ColorRGB& color, float factor) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + s = std::clamp(s * factor, 0.0f, 1.0f); + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGBA saturate(const ColorRGBA& color, float factor) +{ + return to_rgba(saturate(to_rgb(color), factor), color.a()); +} + +ColorRGB opposite(const ColorRGB& color) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + + h += 65.0f; // 65 instead 60 to avoid circle values + if (h > 360.0f) + h -= 360.0f; + + Randomizer rnd; + s = rnd.random_float(0.65f, 1.0f); + v = rnd.random_float(0.65f, 1.0f); + + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGB opposite(const ColorRGB& a, const ColorRGB& b) +{ + float ha, sa, va; + RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va); + float hb, sb, vb; + RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb); + + float delta_h = std::abs(ha - hb); + float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb); + + start_h += 5.0f; // to avoid circle change of colors for 120 deg + if (delta_h < 180.0f) + delta_h = 360.0f - delta_h; + + Randomizer rnd; + float out_h = start_h + 0.5f * delta_h; + if (out_h > 360.0f) + out_h -= 360.0f; + float out_s = rnd.random_float(0.65f, 1.0f); + float out_v = rnd.random_float(0.65f, 1.0f); + + float out_r, out_g, out_b; + HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b); + return { out_r, out_g, out_b }; +} + +bool can_decode_color(const std::string &color) +{ + return (color.size() == 7 && color.front() == '#') || (color.size() == 9 && color.front() == '#'); +} + +bool decode_color(const std::string& color_in, ColorRGB& color_out) +{ + ColorRGBA rgba; + if (!decode_color(color_in, rgba)) + return false; + + color_out = to_rgb(rgba); + return true; +} + +bool decode_color(const std::string& color_in, ColorRGBA& color_out) +{ + auto hex_digit_to_int = [](const char c) { + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + }; + + color_out = ColorRGBA::BLACK(); + if (can_decode_color(color_in)) { + const char *c = color_in.data() + 1; + if (color_in.size() == 7) { + for (unsigned int i = 0; i < 3; ++i) { + const int digit1 = hex_digit_to_int(*c++); + const int digit2 = hex_digit_to_int(*c++); + if (digit1 != -1 && digit2 != -1) + color_out.set(i, float(digit1 * 16 + digit2) * INV_255); + } + } else { + for (unsigned int i = 0; i < 4; ++i) { + const int digit1 = hex_digit_to_int(*c++); + const int digit2 = hex_digit_to_int(*c++); + if (digit1 != -1 && digit2 != -1) + color_out.set(i, float(digit1 * 16 + digit2) * INV_255); + } + } + } else + return false; + + assert(0.0f <= color_out.r() && color_out.r() <= 1.0f); + assert(0.0f <= color_out.g() && color_out.g() <= 1.0f); + assert(0.0f <= color_out.b() && color_out.b() <= 1.0f); + assert(0.0f <= color_out.a() && color_out.a() <= 1.0f); + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGB::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGBA::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +std::string encode_color(const ColorRGB& color) +{ + char buffer[64]; + ::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar()); + return std::string(buffer); +} + +std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); } + +ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; } + +ColorRGBA picking_decode(unsigned int id) +{ + return { + float((id >> 0) & 0xff) * INV_255, // red + float((id >> 8) & 0xff) * INV_255, // green + float((id >> 16) & 0xff) * INV_255, // blue + float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling + }; +} + +unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); } + +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) +{ + // 8 bit hash for the color + unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; + // Increase enthropy by a bit reversal + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + // Flip every second bit to increase the enthropy even more. + b ^= 0x55; + return b; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp new file mode 100644 index 0000000000..ea17328bec --- /dev/null +++ b/src/libslic3r/Color.hpp @@ -0,0 +1,175 @@ +#ifndef slic3r_Color_hpp_ +#define slic3r_Color_hpp_ + +#include +#include + +namespace Slic3r { + +class ColorRGB +{ + std::array m_data{1.0f, 1.0f, 1.0f}; + +public: + ColorRGB() = default; + ColorRGB(float r, float g, float b); + ColorRGB(unsigned char r, unsigned char g, unsigned char b); + ColorRGB(const ColorRGB& other) = default; + + ColorRGB& operator = (const ColorRGB& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGB& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGB& other) const { return !operator==(other); } + bool operator < (const ColorRGB& other) const; + bool operator > (const ColorRGB& other) const; + + ColorRGB operator + (const ColorRGB& other) const; + ColorRGB operator * (float value) const; + + const float* const data() const { return m_data.data(); } + + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 2); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + + static const ColorRGB BLACK() { return { 0.0f, 0.0f, 0.0f }; } + static const ColorRGB BLUE() { return { 0.0f, 0.0f, 1.0f }; } + static const ColorRGB BLUEISH() { return { 0.5f, 0.5f, 1.0f }; } + static const ColorRGB CYAN() { return { 0.0f, 1.0f, 1.0f }; } + static const ColorRGB DARK_GRAY() { return { 0.25f, 0.25f, 0.25f }; } + static const ColorRGB DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f }; } + static const ColorRGB GRAY() { return { 0.5f, 0.5f, 0.5f }; } + static const ColorRGB GREEN() { return { 0.0f, 1.0f, 0.0f }; } + static const ColorRGB GREENISH() { return { 0.5f, 1.0f, 0.5f }; } + static const ColorRGB LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f }; } + static const ColorRGB MAGENTA() { return { 1.0f, 0.0f, 1.0f }; } + static const ColorRGB ORANGE() { return { 0.92f, 0.50f, 0.26f }; } + static const ColorRGB RED() { return { 1.0f, 0.0f, 0.0f }; } + static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; } + static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; } + static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; } + + static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; } + static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; } + static const ColorRGB Z() { return { 0.0f, 0.0f, 0.75f }; } +}; + +class ColorRGBA +{ + std::array m_data{ 1.0f, 1.0f, 1.0f, 1.0f }; + +public: + ColorRGBA() = default; + ColorRGBA(float r, float g, float b, float a); + ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + ColorRGBA(const ColorRGBA& other) = default; + + ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGBA& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGBA& other) const { return !operator==(other); } + bool operator < (const ColorRGBA& other) const; + bool operator > (const ColorRGBA& other) const; + + ColorRGBA operator + (const ColorRGBA& other) const; + ColorRGBA operator * (float value) const; + + const float* const data() const { return m_data.data(); } + + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + float a() const { return m_data[3]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 3); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + unsigned char a_uchar() const { return static_cast(m_data[3] * 255.0f); } + + bool is_transparent() const { return m_data[3] < 1.0f; } + + static const ColorRGBA BLACK() { return { 0.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA BLUE() { return { 0.0f, 0.0f, 1.0f, 1.0f }; } + static const ColorRGBA BLUEISH() { return { 0.5f, 0.5f, 1.0f, 1.0f }; } + static const ColorRGBA CYAN() { return { 0.0f, 1.0f, 1.0f, 1.0f }; } + static const ColorRGBA DARK_GRAY() { return { 0.25f, 0.25f, 0.25f, 1.0f }; } + static const ColorRGBA DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f, 1.0f }; } + static const ColorRGBA GRAY() { return { 0.5f, 0.5f, 0.5f, 1.0f }; } + static const ColorRGBA GREEN() { return { 0.0f, 1.0f, 0.0f, 1.0f }; } + static const ColorRGBA GREENISH() { return { 0.5f, 1.0f, 0.5f, 1.0f }; } + static const ColorRGBA LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f, 1.0f }; } + static const ColorRGBA MAGENTA() { return { 1.0f, 0.0f, 1.0f, 1.0f }; } + static const ColorRGBA ORANGE() { return { 0.923f, 0.504f, 0.264f, 1.0f }; } + static const ColorRGBA RED() { return { 1.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA REDISH() { return { 1.0f, 0.5f, 0.5f, 1.0f }; } + static const ColorRGBA YELLOW() { return { 1.0f, 1.0f, 0.0f, 1.0f }; } + static const ColorRGBA WHITE() { return { 1.0f, 1.0f, 1.0f, 1.0f }; } + static const ColorRGBA ORCA() { return {0.0f, 150.f / 255.0f, 136.0f / 255, 1.0f}; } + + static const ColorRGBA X() { return { 0.75f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA Y() { return { 0.0f, 0.75f, 0.0f, 1.0f }; } + static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; } +}; + +ColorRGB operator * (float value, const ColorRGB& other); +ColorRGBA operator * (float value, const ColorRGBA& other); + +ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t); +ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t); + +ColorRGB complementary(const ColorRGB& color); +ColorRGBA complementary(const ColorRGBA& color); + +ColorRGB saturate(const ColorRGB& color, float factor); +ColorRGBA saturate(const ColorRGBA& color, float factor); + +ColorRGB opposite(const ColorRGB& color); +ColorRGB opposite(const ColorRGB& a, const ColorRGB& b); + +bool can_decode_color(const std::string& color); + +bool decode_color(const std::string& color_in, ColorRGB& color_out); +bool decode_color(const std::string& color_in, ColorRGBA& color_out); + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out); +bool decode_colors(const std::vector& colors_in, std::vector& colors_out); + +std::string encode_color(const ColorRGB& color); +std::string encode_color(const ColorRGBA& color); + +ColorRGB to_rgb(const ColorRGBA& other_rgba); +ColorRGBA to_rgba(const ColorRGB& other_rgb); +ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha); + +ColorRGBA picking_decode(unsigned int id); +unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b); +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); + +} // namespace Slic3r + +#endif /* slic3r_Color_hpp_ */ diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp new file mode 100644 index 0000000000..cdda3097ac --- /dev/null +++ b/src/libslic3r/CutUtils.cpp @@ -0,0 +1,662 @@ +///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ + +#include "CutUtils.hpp" +#include "Geometry.hpp" +#include "libslic3r.h" +#include "Model.hpp" +#include "TriangleMeshSlicer.hpp" +#include "TriangleSelector.hpp" +#include "ObjectID.hpp" + +#include + +namespace Slic3r { + +using namespace Geometry; + +static void apply_tolerance(ModelVolume* vol) +{ + ModelVolume::CutInfo& cut_info = vol->cut_info; + + assert(cut_info.is_connector); + if (!cut_info.is_processed) + return; + + Vec3d sf = vol->get_scaling_factor(); + + // make a "hole" wider + sf[X] += double(cut_info.radius_tolerance); + sf[Y] += double(cut_info.radius_tolerance); + + // make a "hole" dipper + sf[Z] += double(cut_info.height_tolerance); + + vol->set_scaling_factor(sf); + + // correct offset in respect to the new depth + Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ(); + if (rot_norm.norm() != 0.0) + rot_norm.normalize(); + + double z_offset = 0.5 * static_cast(cut_info.height_tolerance); + if (cut_info.connector_type == CutConnectorType::Plug || + cut_info.connector_type == CutConnectorType::Snap) + z_offset -= 0.05; // add small Z offset to better preview + + vol->set_offset(vol->get_offset() + rot_norm * z_offset); +} + +static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART) +{ + if (mesh.empty()) + return; + + mesh.transform(cut_matrix); + ModelVolume* vol = object->add_volume(mesh); + vol->set_type(type); + + vol->name = src_volume->name + suffix; + // Don't copy the config's ID. + vol->config.assign_config(src_volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != src_volume->config.id()); + vol->set_material(src_volume->material_id(), *src_volume->material()); + vol->cut_info = src_volume->cut_info; +} + +static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) +{ + const auto volume_matrix = volume->get_matrix(); + + const Transformation cut_transformation = Transformation(cut_matrix); + const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset()); + + // Transform the mesh by the combined transformation matrix. + // Flip the triangles in case the composite transformation is left handed. + TriangleMesh mesh(volume->mesh()); + mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); + + indexed_triangle_set upper_its, lower_its; + cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper_mesh = TriangleMesh(upper_its); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower_mesh = TriangleMesh(lower_its); +} + +static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + std::vector& dowels) +{ + assert(volume->cut_info.is_connector); + volume->cut_info.set_processed(); + + const auto volume_matrix = volume->get_matrix(); + + // ! Don't apply instance transformation for the conntectors. + // This transformation is already there + if (volume->cut_info.connector_type != CutConnectorType::Dowel) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + ModelVolume* vol = nullptr; + if (volume->cut_info.connector_type == CutConnectorType::Snap) { + TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); + + vol = upper->add_volume(std::move(mesh)); + vol->set_transformation(volume->get_transformation()); + vol->set_type(ModelVolumeType::NEGATIVE_VOLUME); + + vol->cut_info = volume->cut_info; + vol->name = volume->name; + } + else + vol = upper->add_volume(*volume); + + vol->set_transformation(volume_matrix); + apply_tolerance(vol); + } + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + ModelVolume* vol = lower->add_volume(*volume); + vol->set_transformation(volume_matrix); + // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug + vol->set_type(ModelVolumeType::MODEL_PART); + } + } + else { + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { + ModelObject* dowel{ nullptr }; + // Clone the object to duplicate instances, materials etc. + volume->get_object()->clone_for_cut(&dowel); + + // add one more solid part same as connector if this connector is a dowel + ModelVolume* vol = dowel->add_volume(*volume); + vol->set_type(ModelVolumeType::MODEL_PART); + + // But discard rotation and Z-offset for this volume + vol->set_rotation(Vec3d::Zero()); + vol->set_offset(Z, 0.0); + + dowels.push_back(dowel); + } + + // Cut the dowel + apply_tolerance(volume); + + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); + + // add small Z offset to better preview + upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); + lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); + + // Add cut parts to the related objects + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); + add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); + } +} + +static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +{ + const auto volume_matrix = instance_matrix * volume->get_matrix(); + + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + volume->set_transformation(Transformation(volume_matrix)); + + if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { + upper->add_volume(*volume); + return; + } + + // Some logic for the negative volumes/connectors. Add only needed modifiers + auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); + bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) + lower->add_volume(*volume); +} + +static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +{ + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); + + // Add required cut parts to the objects + + if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + if (!lower_mesh.empty()) { + add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + upper->volumes.back()->cut_info.is_from_upper = false; + } + return; + } + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + add_cut_volume(upper_mesh, upper, volume, cut_matrix); + + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) + add_cut_volume(lower_mesh, lower, volume, cut_matrix); +} + +static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, + const Transform3d& cut_matrix = Transform3d::Identity(), + bool place_on_cut = false, bool flip = false) +{ + // Reset instance transformation except offset and Z-rotation + + for (size_t i = 0; i < object->instances.size(); ++i) { + auto& obj_instance = object->instances[i]; + const double rot_z = obj_instance->get_rotation().z(); + + Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix(false, false, true)); + // add respect to mirroring + if (obj_instance->is_left_handed()) + inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1))); + + obj_instance->set_transformation(inst_trafo); + + Vec3d rotation = Vec3d::Zero(); + if (!flip && !place_on_cut) { + if ( i != src_instance_idx) + rotation[Z] = rot_z; + } + else { + Transform3d rotation_matrix = Transform3d::Identity(); + if (flip) + rotation_matrix = rotation_transform(PI * Vec3d::UnitX()); + + if (place_on_cut) + rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse(); + + if (i != src_instance_idx) + rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix; + + rotation = Transformation(rotation_matrix).get_rotation(); + } + + obj_instance->set_rotation(rotation); + } +} + + +Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/) + : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes) +{ + m_model = Model(); + if (object) + m_model.add_object(*object); +} + +void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip) +{ + if (!object) return; + + if (keep && !object->volumes.empty()) { + reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip); + cut_object_ptrs.push_back(object); + } + else + m_model.objects.push_back(object); // will be deleted in m_model.clear_objects(); +} + +void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs) +{ + post_process(upper, cut_object_ptrs, + m_attributes.has(ModelObjectCutAttribute::KeepUpper), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), + m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + + post_process(lower, cut_object_ptrs, + m_attributes.has(ModelObjectCutAttribute::KeepLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); +} + + +void Cut::finalize(const ModelObjectPtrs& objects) +{ + //clear model from temporarry objects + m_model.clear_objects(); + + // add to model result objects + m_model.objects = objects; +} + + +const ModelObjectPtrs& Cut::perform_with_plane() +{ + if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) { + m_model.clear_objects(); + return m_model.objects; + } + + ModelObject* mo = m_model.objects.front(); + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) + mo->clone_for_cut(&upper); + + ModelObject* lower{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::KeepAsParts)) + mo->clone_for_cut(&lower); + + std::vector dowels; + + // Because transformations are going to be applied to meshes directly, + // we reset transformation of all instances and volumes, + // except for translation and Z-rotation on instances, which are preserved + // in the transformation matrix and not applied to the mesh transform. + + const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix(true); + const Transformation cut_transformation = Transformation(m_cut_matrix); + const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); + + for (ModelVolume* volume : mo->volumes) { + volume->reset_extra_facets(); + + if (!volume->is_model_part()) { + if (volume->cut_info.is_processed) + process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower); + else + process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); + } + else if (!volume->mesh().empty()) + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); + } + + // Post-process cut parts + + if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) { + m_model = Model(); + m_model.objects.push_back(upper); + return m_model.objects; + } + + ModelObjectPtrs cut_object_ptrs; + + if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) { + reset_instance_transformation(upper, m_instance, m_cut_matrix); + cut_object_ptrs.push_back(upper); + } + else { + // Delete all modifiers which are not intersecting with solid parts bounding box + auto delete_extra_modifiers = [this](ModelObject* mo) { + if (!mo) return; + const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance); + const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix(); + + for (int i = int(mo->volumes.size()) - 1; i >= 0; --i) + if (const ModelVolume* vol = mo->volumes[i]; + !vol->is_model_part() && !vol->is_cut_connector()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + if (!obj_bb.intersects(bb)) + mo->delete_volume(i); + } + }; + + post_process(upper, lower, cut_object_ptrs); + delete_extra_modifiers(upper); + delete_extra_modifiers(lower); + + if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { + for (auto dowel : dowels) { + reset_instance_transformation(dowel, m_instance); + dowel->name += "-Dowel-" + dowel->volumes[0]->name; + cut_object_ptrs.push_back(dowel); + } + } + } + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + + finalize(cut_object_ptrs); + + return m_model.objects; +} + +static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2) +{ + auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3(); + auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3(); + const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix(); + + for (ModelVolume* vol : from_obj->volumes) + if (!vol->is_model_part()) { + // Don't add modifiers which are processed connectors + if (vol->cut_info.is_connector && !vol->cut_info.is_processed) + continue; + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + // Don't add modifiers which are not intersecting with solid parts + if (obj1_bb.intersects(bb)) + to_obj1->add_volume(*vol); + if (obj2_bb.intersects(bb)) + to_obj2->add_volume(*vol); + } +} + +static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) +{ + for (ModelObject* mo : objects) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume* mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) + mo->delete_volume(i); + } + // Ensuring that volumes start with solid parts for proper slicing + mo->sort_volumes(true); + } + } +} + + +const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowels_count) +{ + ModelObject* cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower); + + const size_t cut_parts_cnt = parts.size(); + bool has_modifiers = false; + + // Distribute SolidParts to the Upper/Lower object + for (size_t id = 0; id < cut_parts_cnt; ++id) { + if (parts[id].is_modifier) + has_modifiers = true; // modifiers will be added later to the related parts + else if (ModelObject* obj = (parts[id].selected ? upper : lower)) + obj->add_volume(*(cut_mo->volumes[id])); + } + + if (has_modifiers) { + // Distribute Modifiers to the Upper/Lower object + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + } + + ModelObjectPtrs cut_object_ptrs; + + ModelVolumePtrs& volumes = cut_mo->volumes; + if (volumes.size() == cut_parts_cnt) { + // Means that object is cut without connectors + + // Just add Upper and Lower objects to cut_object_ptrs + post_process(upper, lower, cut_object_ptrs); + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + + // replace initial objects in model with cut object + finalize(cut_object_ptrs); + } + else if (volumes.size() > cut_parts_cnt) { + // Means that object is cut with connectors + + // All volumes are distributed to Upper / Lower object, + // So we don’t need them anymore + for (size_t id = 0; id < cut_parts_cnt; id++) + delete* (volumes.begin() + id); + volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); + + // Perform cut just to get connectors + Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes); + const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane(); + assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); + + // Connectors from upper object + for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) + upper->add_volume(*volume, volume->type()); + + // Connectors from lower object + for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) + lower->add_volume(*volume, volume->type()); + + // Add Upper and Lower objects to cut_object_ptrs + post_process(upper, lower, cut_object_ptrs); + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + + // replace initial objects in model with cut object + finalize(cut_object_ptrs); + + // Add Dowel-connectors as separate objects to model + if (cut_connectors_obj.size() >= 3) + for (size_t id = 2; id < cut_connectors_obj.size(); id++) + m_model.add_object(*cut_connectors_obj[id]); + } + + return m_model.objects; +} + + +const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/) +{ + ModelObject* cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + cut_mo->clone_for_cut(&lower); + + const double groove_half_depth = 0.5 * double(groove.depth); + + Model tmp_model_for_cut = Model(); + + Model tmp_model = Model(); + tmp_model.add_object(*cut_mo); + ModelObject* tmp_object = tmp_model.objects.front(); + + auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { + const auto& volumes = tmp_model_for_cut.objects.front()->volumes; + for (const ModelVolume* volume : volumes) + if (volume->is_model_part()) { + if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || + (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) { + ModelVolume* new_vol = object->add_volume(*volume); + new_vol->reset_from_upper(); + } + } + }; + + auto cut = [this, add_volumes_from_cut] + (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) { + Cut cut(object, m_instance, cut_matrix); + + tmp_model_for_cut = Model(); + tmp_model_for_cut.add_object(*cut.perform_with_plane().front()); + assert(!tmp_model_for_cut.objects.empty()); + + object->clear_volumes(); + add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); + reset_instance_transformation(object, m_instance); + }; + + // cut by upper plane + + const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by lower plane + + const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + } + + // cut middle part with 2 angles and add parts to related upper/lower objects + + const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + + // cut by angle1 plane + { + const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + + cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by angle2 plane + { + const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + + cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // apply tolerance to the middle part + { + const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance; + + const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix; + cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + + const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); + + const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // this part can be added to the upper object now + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + ModelObjectPtrs cut_object_ptrs; + + if (keep_as_parts) { + // add volumes from lower object to the upper, but mark them as a lower + const auto& volumes = lower->volumes; + for (const ModelVolume* volume : volumes) { + ModelVolume* new_vol = upper->add_volume(*volume); + new_vol->cut_info.is_from_upper = false; + } + + // add modifiers + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) + upper->add_volume(*volume); + + cut_object_ptrs.push_back(upper); + + // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks + cut_object_ptrs.push_back(lower); + } + else { + // add modifiers if object has any + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) { + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + break; + } + + assert(!upper->volumes.empty() && !lower->volumes.empty()); + + // Add Upper and Lower parts to cut_object_ptrs + + post_process(upper, lower, cut_object_ptrs); + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + } + + finalize(cut_object_ptrs); + + return m_model.objects; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp new file mode 100644 index 0000000000..5067aaaff1 --- /dev/null +++ b/src/libslic3r/CutUtils.hpp @@ -0,0 +1,70 @@ +///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_CutUtils_hpp_ +#define slic3r_CutUtils_hpp_ + +#include "enum_bitmask.hpp" +#include "Point.hpp" +#include "Model.hpp" + +#include + +namespace Slic3r { + +using ModelObjectPtrs = std::vector; + +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo }; +using ModelObjectCutAttributes = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); + + +class Cut { + + Model m_model; + int m_instance; + const Transform3d m_cut_matrix; + ModelObjectCutAttributes m_attributes; + + void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip); + void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects); + void finalize(const ModelObjectPtrs& objects); + +public: + + Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts ); + ~Cut() { m_model.clear_objects(); } + + struct Groove + { + float depth{ 0.f }; + float width{ 0.f }; + float flaps_angle{ 0.f }; + float angle{ 0.f }; + float depth_init{ 0.f }; + float width_init{ 0.f }; + float flaps_angle_init{ 0.f }; + float angle_init{ 0.f }; + float depth_tolerance{ 0.1f }; + float width_tolerance{ 0.1f }; + }; + + struct Part + { + bool selected; + bool is_modifier; + }; + + const ModelObjectPtrs& perform_with_plane(); + const ModelObjectPtrs& perform_by_contour(std::vector parts, int dowels_count); + const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false); + +}; // namespace Cut + +} // namespace Slic3r + +#endif /* slic3r_CutUtils_hpp_ */ diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 391ac2d587..e6ae1edd4d 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -56,12 +56,9 @@ ExtrusionEntityCollection::operator ExtrusionPaths() const return paths; } -ExtrusionEntity* ExtrusionEntityCollection::clone() const +ExtrusionEntity *ExtrusionEntityCollection::clone() const { - ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this); - for (size_t i = 0; i < coll->entities.size(); ++i) - coll->entities[i] = this->entities[i]->clone(); - return coll; + return new ExtrusionEntityCollection(*this); } void ExtrusionEntityCollection::reverse() diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 6be38e14a1..c616d0f1b4 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -1,3 +1,8 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros +///|/ Copyright (c) 2020 Henner Zeller +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "../libslic3r.h" #include "../Exception.hpp" #include "../Model.hpp" @@ -741,8 +746,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) { int volume_id; int type; - float radius; - float height; float r_tolerance; float h_tolerance; }; @@ -758,10 +761,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //typedef std::map IdToAliasesMap; typedef std::vector InstancesList; typedef std::map IdToMetadataMap; - typedef std::map IdToCutObjectInfoMap; //typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; typedef std::map IdToLayerConfigRangesMap; + typedef std::map IdToCutObjectInfoMap; /*typedef std::map> IdToSlaSupportPointsMap; typedef std::map> IdToSlaDrainHolesMap;*/ @@ -951,7 +954,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //IdToGeometryMap m_orig_geometries; // backup & restore CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; - IdToCutObjectInfoMap m_cut_object_infos; + IdToCutObjectInfoMap m_cut_object_infos; IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerConfigRangesMap m_layer_config_ranges; /*IdToSlaSupportPointsMap m_sla_support_points; @@ -1013,7 +1016,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions); + void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); @@ -1955,11 +1958,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1); if (cut_object_info != m_cut_object_infos.end()) { model_object->cut_id = cut_object_info->second.id; - + int vol_cnt = int(model_object->volumes.size()); for (auto connector : cut_object_info->second.connectors) { - assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size())); - model_object->volumes[connector.volume_id]->cut_info = - ModelVolume::CutInfo(CutConnectorType(connector.type), connector.radius, connector.height, connector.r_tolerance, connector.h_tolerance, true); + if (connector.volume_id < 0 || connector.volume_id >= vol_cnt) { + add_error("Invalid connector is found"); + continue; + } + model_object->volumes[connector.volume_id]->cut_info = + ModelVolume::CutInfo(CutConnectorType(connector.type), connector.r_tolerance, connector.h_tolerance, true); } } } @@ -2324,7 +2330,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) void _BBS_3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions) { if (stat.m_uncomp_size > 0) { - std::string buffer((size_t) stat.m_uncomp_size, 0); + std::string buffer((size_t)stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void *) buffer.data(), (size_t) stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading cut information data to buffer"); @@ -2332,12 +2338,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } std::istringstream iss(buffer); // wrap returned xml to istringstream - pt::ptree objects_tree; + pt::ptree objects_tree; pt::read_xml(iss, objects_tree); - for (const auto &object : objects_tree.get_child("objects")) { + for (const auto& object : objects_tree.get_child("objects")) { pt::ptree object_tree = object.second; - int obj_idx = object_tree.get(".id", -1); + int obj_idx = object_tree.get(".id", -1); if (obj_idx <= 0) { add_error("Found invalid object id"); continue; @@ -2349,30 +2355,33 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) continue; } - CutObjectBase cut_id; - std::vector connectors; + CutObjectBase cut_id; + std::vector connectors; - for (const auto &obj_cut_info : object_tree) { + for (const auto& obj_cut_info : object_tree) { if (obj_cut_info.first == "cut_id") { pt::ptree cut_id_tree = obj_cut_info.second; - cut_id = CutObjectBase(ObjectID(cut_id_tree.get(".id")), cut_id_tree.get(".check_sum"), - cut_id_tree.get(".connectors_cnt")); + cut_id = CutObjectBase(ObjectID( cut_id_tree.get(".id")), + cut_id_tree.get(".check_sum"), + cut_id_tree.get(".connectors_cnt")); } if (obj_cut_info.first == "connectors") { pt::ptree cut_connectors_tree = obj_cut_info.second; - for (const auto &cut_connector : cut_connectors_tree) { - if (cut_connector.first != "connector") continue; - pt::ptree connector_tree = cut_connector.second; - CutObjectInfo::Connector connector = {connector_tree.get(".volume_id"), connector_tree.get(".type"), - connector_tree.get(".radius", 0.f), connector_tree.get(".height", 0.f), - connector_tree.get(".r_tolerance"), connector_tree.get(".h_tolerance")}; + for (const auto& cut_connector : cut_connectors_tree) { + if (cut_connector.first != "connector") + continue; + pt::ptree connector_tree = cut_connector.second; + CutObjectInfo::Connector connector = {connector_tree.get(".volume_id"), + connector_tree.get(".type"), + connector_tree.get(".r_tolerance"), + connector_tree.get(".h_tolerance")}; connectors.emplace_back(connector); } } } - CutObjectInfo cut_info{cut_id, connectors}; - m_cut_object_infos.insert({obj_idx, cut_info}); + CutObjectInfo cut_info {cut_id, connectors}; + m_cut_object_infos.insert({ obj_idx, cut_info }); } } } @@ -5260,6 +5269,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //BBS: change volume to seperate objects bool _add_mesh_to_object_stream(std::function const &flush, ObjectData const &object_data) const; bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const; + bool _add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); @@ -5270,7 +5280,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //BBS: add project embedded preset files bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector project_presets); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false); - bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model); bool _add_slice_info_config_file_to_archive(mz_zip_archive &archive, const Model &model, PlateDataPtrs &plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config); bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); @@ -6233,7 +6242,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) continue; volume_count++; if (m_share_mesh) { - auto iter = m_shared_meshes.find(volume->mesh_ptr()); + auto iter = m_shared_meshes.find(volume->mesh_ptr().get()); if (iter != m_shared_meshes.end()) { const ModelVolume* shared_volume = iter->second.second; @@ -6248,7 +6257,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) continue; } } - const_cast<_BBS_3MF_Exporter *>(this)->m_shared_meshes.insert({volume->mesh_ptr(), {&object_data, volume}}); + const_cast<_BBS_3MF_Exporter *>(this)->m_shared_meshes.insert({volume->mesh_ptr().get(), {&object_data, volume}}); } if (m_from_backup_save) volume_id = (volume_count << 16 | backup_id); @@ -6704,6 +6713,69 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model) + { + std::string out = ""; + pt::ptree tree; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) { + object_cnt++; + if (!object->is_cut()) + continue; + pt::ptree& obj_tree = tree.add("objects.object", ""); + + obj_tree.put(".id", object_cnt); + + // Store info for cut_id + pt::ptree& cut_id_tree = obj_tree.add("cut_id", ""); + + // store cut_id atributes + cut_id_tree.put(".id", object->cut_id.id().id); + cut_id_tree.put(".check_sum", object->cut_id.check_sum()); + cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt()); + + int volume_idx = -1; + for (const ModelVolume* volume : object->volumes) { + ++volume_idx; + if (volume->is_cut_connector()) { + pt::ptree& connectors_tree = obj_tree.add("connectors.connector", ""); + connectors_tree.put(".volume_id", volume_idx); + connectors_tree.put(".type", int(volume->cut_info.connector_type)); + connectors_tree.put(".r_tolerance", volume->cut_info.radius_tolerance); + connectors_tree.put(".h_tolerance", volume->cut_info.height_tolerance); + } + } + } + + if (!tree.empty()) { + std::ostringstream oss; + pt::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string for a better preview + boost::replace_all(out, ">\n \n ", ">\n "); + boost::replace_all(out, ">\n ", ">\n "); + boost::replace_all(out, ">\n ", ">\n "); + boost::replace_all(out, ">", ">\n "); + // OR just + boost::replace_all(out, "><", ">\n<"); + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add cut information file to archive"); + return false; + } + } + + return true; + } + bool _BBS_3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) { assert(is_decimal_separator_point()); @@ -7266,69 +7338,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } - bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model) - { - std::string out = ""; - pt::ptree tree; - - unsigned int object_cnt = 0; - for (const ModelObject *object : model.objects) { - object_cnt++; - pt::ptree &obj_tree = tree.add("objects.object", ""); - - obj_tree.put(".id", object_cnt); - - // Store info for cut_id - pt::ptree &cut_id_tree = obj_tree.add("cut_id", ""); - - // store cut_id atributes - cut_id_tree.put(".id", object->cut_id.id().id); - cut_id_tree.put(".check_sum", object->cut_id.check_sum()); - cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt()); - - int volume_idx = -1; - for (const ModelVolume *volume : object->volumes) { - ++volume_idx; - if (volume->is_cut_connector()) { - pt::ptree &connectors_tree = obj_tree.add("connectors.connector", ""); - connectors_tree.put(".volume_id", volume_idx); - connectors_tree.put(".type", int(volume->cut_info.connector_type)); - connectors_tree.put(".radius", volume->cut_info.radius); - connectors_tree.put(".height", volume->cut_info.height); - connectors_tree.put(".r_tolerance", volume->cut_info.radius_tolerance); - connectors_tree.put(".h_tolerance", volume->cut_info.height_tolerance); - } - } - } - - if (!tree.empty()) { - std::ostringstream oss; - pt::write_xml(oss, tree); - out = oss.str(); - - // Post processing("beautification") of the output string for a better preview - boost::replace_all(out, ">\n \n ", ">\n "); - boost::replace_all(out, ">\n ", ">\n "); - boost::replace_all(out, ">\n ", ">\n "); - boost::replace_all(out, ">", ">\n "); - // OR just - boost::replace_all(out, "><", ">\n<"); - } - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void *) out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add cut information file to archive"); - return false; - } - } - - return true; - } - bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config) { std::stringstream stream; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index a2c59ec17d..e57b774f34 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1,3 +1,17 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Tomáš Mészáros @tamasmeszaros +///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel +///|/ +///|/ ported from lib/Slic3r/Geometry.pm: +///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv +///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2013 Jose Luis Perez Diez +///|/ Copyright (c) 2013 Anders Sundman +///|/ Copyright (c) 2013 Jesse Vincent +///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake +///|/ Copyright (c) 2012 Mark Hindess +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r.h" #include "Exception.hpp" #include "Geometry.hpp" @@ -320,46 +334,103 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, return transform; } +void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) +{ + transform = translation * rotation * scale * mirror; +} + +Transform3d assemble_transform(const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) +{ + Transform3d transform; + assemble_transform(transform, translation, rotation, scale, mirror); + return transform; +} + +void translation_transform(Transform3d& transform, const Vec3d& translation) +{ + transform = Transform3d::Identity(); + transform.translate(translation); +} + +Transform3d translation_transform(const Vec3d& translation) +{ + Transform3d transform; + translation_transform(transform, translation); + return transform; +} + +void rotation_transform(Transform3d& transform, const Vec3d& rotation) +{ + transform = Transform3d::Identity(); + transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); +} + +Transform3d rotation_transform(const Vec3d& rotation) +{ + Transform3d transform; + rotation_transform(transform, rotation); + return transform; +} + +void scale_transform(Transform3d& transform, double scale) +{ + return scale_transform(transform, scale * Vec3d::Ones()); +} + +void scale_transform(Transform3d& transform, const Vec3d& scale) +{ + transform = Transform3d::Identity(); + transform.scale(scale); +} + +Transform3d scale_transform(double scale) +{ + return scale_transform(scale * Vec3d::Ones()); +} + +Transform3d scale_transform(const Vec3d& scale) +{ + Transform3d transform; + scale_transform(transform, scale); + return transform; +} + Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { // reference: http://www.gregslabaugh.net/publications/euler.pdf Vec3d angles1 = Vec3d::Zero(); Vec3d angles2 = Vec3d::Zero(); // BBS: rotation_matrix(2, 0) may be slighterly larger than 1 due to numerical accuracy - if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5 || std::abs(rotation_matrix(2, 0))>1) - { - angles1(2) = 0.0; - if (rotation_matrix(2, 0) < 0.0) // == -1.0 - { - angles1(1) = 0.5 * (double)PI; - angles1(0) = angles1(2) + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); + if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5 || std::abs(rotation_matrix(2, 0))>1) { + angles1.z() = 0.0; + if (rotation_matrix(2, 0) < 0.0) { // == -1.0 + angles1.y() = 0.5 * double(PI); + angles1.x() = angles1.z() + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); } - else // == 1.0 - { - angles1(1) = - 0.5 * (double)PI; - angles1(0) = - angles1(2) + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2)); + else { // == 1.0 + angles1.y() = - 0.5 * double(PI); + angles1.x() = - angles1.y() + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2)); } angles2 = angles1; } - else - { - angles1(1) = -::asin(rotation_matrix(2, 0)); - double inv_cos1 = 1.0 / ::cos(angles1(1)); - angles1(0) = ::atan2(rotation_matrix(2, 1) * inv_cos1, rotation_matrix(2, 2) * inv_cos1); - angles1(2) = ::atan2(rotation_matrix(1, 0) * inv_cos1, rotation_matrix(0, 0) * inv_cos1); + else { + angles1.y() = -::asin(rotation_matrix(2, 0)); + const double inv_cos1 = 1.0 / ::cos(angles1.y()); + angles1.x() = ::atan2(rotation_matrix(2, 1) * inv_cos1, rotation_matrix(2, 2) * inv_cos1); + angles1.z() = ::atan2(rotation_matrix(1, 0) * inv_cos1, rotation_matrix(0, 0) * inv_cos1); - angles2(1) = (double)PI - angles1(1); - double inv_cos2 = 1.0 / ::cos(angles2(1)); - angles2(0) = ::atan2(rotation_matrix(2, 1) * inv_cos2, rotation_matrix(2, 2) * inv_cos2); - angles2(2) = ::atan2(rotation_matrix(1, 0) * inv_cos2, rotation_matrix(0, 0) * inv_cos2); + angles2.y() = double(PI) - angles1.y(); + const double inv_cos2 = 1.0 / ::cos(angles2.y()); + angles2.x() = ::atan2(rotation_matrix(2, 1) * inv_cos2, rotation_matrix(2, 2) * inv_cos2); + angles2.z() = ::atan2(rotation_matrix(1, 0) * inv_cos2, rotation_matrix(0, 0) * inv_cos2); } // The following euristic is the best found up to now (in the sense that it works fine with the greatest number of edge use-cases) // but there are other use-cases were it does not // We need to improve it - double min_1 = angles1.cwiseAbs().minCoeff(); - double min_2 = angles2.cwiseAbs().minCoeff(); - bool use_1 = (min_1 < min_2) || (is_approx(min_1, min_2) && (angles1.norm() <= angles2.norm())); + const double min_1 = angles1.cwiseAbs().minCoeff(); + const double min_2 = angles2.cwiseAbs().minCoeff(); + const bool use_1 = (min_1 < min_2) || (is_approx(min_1, min_2) && (angles1.norm() <= angles2.norm())); return use_1 ? angles1 : angles2; } @@ -375,6 +446,14 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } +static Transform3d extract_rotation_matrix(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(rotation); +} + void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix) { double epsilon = 1e-5; @@ -409,28 +488,6 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, doubl } } -Transform3d translation_transform(const Vec3d &translation) -{ - Transform3d transform = Transform3d::Identity(); - transform.translate(translation); - return transform; -} - -Transform3d rotation_transform(const Vec3d& rotation) -{ - Transform3d transform = Transform3d::Identity(); - transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); - return transform; -} - -Transformation::Flags::Flags() - : dont_translate(true) - , dont_rotate(true) - , dont_scale(true) - , dont_mirror(true) -{ -} - bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror); @@ -456,35 +513,38 @@ Transformation::Transformation(const Transform3d& transform) void Transformation::set_offset(const Vec3d& offset) { - set_offset(X, offset(0)); - set_offset(Y, offset(1)); - set_offset(Z, offset(2)); + set_offset(X, offset.x()); + set_offset(Y, offset.y()); + set_offset(Z, offset.z()); } void Transformation::set_offset(Axis axis, double offset) { - if (m_offset(axis) != offset) - { + if (m_offset(axis) != offset) { m_offset(axis) = offset; m_dirty = true; } } +Transform3d Transformation::get_rotation_matrix() const +{ + return extract_rotation_matrix(m_matrix); +} + void Transformation::set_rotation(const Vec3d& rotation) { - set_rotation(X, rotation(0)); - set_rotation(Y, rotation(1)); - set_rotation(Z, rotation(2)); + set_rotation(X, rotation.x()); + set_rotation(Y, rotation.y()); + set_rotation(Z, rotation.z()); } void Transformation::set_rotation(Axis axis, double rotation) { rotation = angle_to_0_2PI(rotation); - if (is_approx(std::abs(rotation), 2.0 * (double)PI)) + if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; - if (m_rotation(axis) != rotation) - { + if (m_rotation(axis) != rotation) { m_rotation(axis) = rotation; m_dirty = true; } @@ -492,15 +552,14 @@ void Transformation::set_rotation(Axis axis, double rotation) void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { - set_scaling_factor(X, scaling_factor(0)); - set_scaling_factor(Y, scaling_factor(1)); - set_scaling_factor(Z, scaling_factor(2)); + set_scaling_factor(X, scaling_factor.x()); + set_scaling_factor(Y, scaling_factor.y()); + set_scaling_factor(Z, scaling_factor.z()); } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { - if (m_scaling_factor(axis) != std::abs(scaling_factor)) - { + if (m_scaling_factor(axis) != std::abs(scaling_factor)) { m_scaling_factor(axis) = std::abs(scaling_factor); m_dirty = true; } @@ -508,9 +567,9 @@ void Transformation::set_scaling_factor(Axis axis, double scaling_factor) void Transformation::set_mirror(const Vec3d& mirror) { - set_mirror(X, mirror(0)); - set_mirror(Y, mirror(1)); - set_mirror(Z, mirror(2)); + set_mirror(X, mirror.x()); + set_mirror(Y, mirror.y()); + set_mirror(Z, mirror.z()); } void Transformation::set_mirror(Axis axis, double mirror) @@ -521,8 +580,7 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; - if (m_mirror(axis) != mirror) - { + if (m_mirror(axis) != mirror) { m_mirror(axis) = mirror; m_dirty = true; } @@ -540,9 +598,8 @@ void Transformation::set_from_transform(const Transform3d& transform) // we can only detect if the matrix contains a left handed reference system // in which case we reorient it back to right handed by mirroring the x axis Vec3d mirror = Vec3d::Ones(); - if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) - { - mirror(0) = -1.0; + if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) { + mirror.x() = -1.0; // remove mirror m3x3.col(0) *= -1.0; } @@ -579,8 +636,7 @@ void Transformation::reset() const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { - if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) - { + if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) { m_matrix = Geometry::assemble_transform( dont_translate ? Vec3d::Zero() : m_offset, dont_rotate ? Vec3d::Zero() : m_rotation, @@ -609,8 +665,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation // Just set the inverse. out.set_from_transform(instance_transformation.get_matrix(true).inverse()); } - else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) - { + else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) { // Anisotropic scaling, rotation by multiples of ninety degrees. Eigen::Matrix3d instance_rotation_trafo = (Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) * @@ -643,8 +698,8 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i)); out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo)); - out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2)))); - out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1)); + out.set_scaling_factor(Vec3d(std::abs(scale.x()), std::abs(scale.y()), std::abs(scale.z()))); + out.set_mirror(Vec3d(scale.x() > 0 ? 1. : -1, scale.y() > 0 ? 1. : -1, scale.z() > 0 ? 1. : -1)); } else { @@ -663,19 +718,15 @@ Transform3d transform3d_from_string(const std::string& transform_str) assert(is_decimal_separator_point()); // for atof Transform3d transform = Transform3d::Identity(); - if (!transform_str.empty()) - { + if (!transform_str.empty()) { std::vector mat_elements_str; boost::split(mat_elements_str, transform_str, boost::is_any_of(" "), boost::token_compress_on); - unsigned int size = (unsigned int)mat_elements_str.size(); - if (size == 16) - { + const unsigned int size = (unsigned int)mat_elements_str.size(); + if (size == 16) { unsigned int i = 0; - for (unsigned int r = 0; r < 4; ++r) - { - for (unsigned int c = 0; c < 4; ++c) - { + for (unsigned int r = 0; r < 4; ++r) { + for (unsigned int c = 0; c < 4; ++c) { transform(r, c) = ::atof(mat_elements_str[i++].c_str()); } } @@ -689,17 +740,17 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot { return // From the current coordinate system to world. - Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) * + Eigen::AngleAxisd(rot_xyz_to.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to.x(), Vec3d::UnitX()) * // From world to the initial coordinate system. - Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ()); + Eigen::AngleAxisd(-rot_xyz_from.x(), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from.z(), Vec3d::UnitZ()); } // This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { - Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - Vec3d axis = angle_axis.axis(); - double angle = angle_axis.angle(); + const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + const Vec3d axis = angle_axis.axis(); + const double angle = angle_axis.angle(); #ifndef NDEBUG if (std::abs(angle) > 1e-8) { assert(std::abs(axis.x()) < 1e-8); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 8eb6195a10..4f6511ff6f 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -1,3 +1,18 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Lukáš Hejl @hejllukas +///|/ Copyright (c) 2017 Eyal Soha @eyal0 +///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel +///|/ +///|/ ported from lib/Slic3r/Geometry.pm: +///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv +///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2013 Jose Luis Perez Diez +///|/ Copyright (c) 2013 Anders Sundman +///|/ Copyright (c) 2013 Jesse Vincent +///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake +///|/ Copyright (c) 2012 Mark Hindess +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_Geometry_hpp_ #define slic3r_Geometry_hpp_ @@ -324,7 +339,8 @@ bool arrange( // 4) rotate Y // 5) rotate Z // 6) translate -void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), + const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); // Returns the transform obtained by assembling the given transformations in the following order: // 1) mirror @@ -333,7 +349,45 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d // 4) rotate Y // 5) rotate Z // 6) translate -Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), + const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); + +// Sets the given transform by multiplying the given transformations in the following order: +// T = translation * rotation * scale * mirror +void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(), + const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(), + const Transform3d& mirror = Transform3d::Identity()); + +// Returns the transform obtained by multiplying the given transformations in the following order: +// T = translation * rotation * scale * mirror +Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(), + const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity()); + +// Sets the given transform by assembling the given translation +void translation_transform(Transform3d& transform, const Vec3d& translation); + +// Returns the transform obtained by assembling the given translation +Transform3d translation_transform(const Vec3d& translation); + +// Sets the given transform by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +void rotation_transform(Transform3d& transform, const Vec3d& rotation); + +// Returns the transform obtained by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +Transform3d rotation_transform(const Vec3d& rotation); + +// Sets the given transform by assembling the given scale factors +void scale_transform(Transform3d& transform, double scale); +void scale_transform(Transform3d& transform, const Vec3d& scale); + +// Returns the transform obtained by assembling the given scale factors +Transform3d scale_transform(double scale); +Transform3d scale_transform(const Vec3d& scale); // Returns the euler angles extracted from the given rotation matrix // Warning -> The matrix should not contain any scale or shear !!! @@ -346,40 +400,29 @@ Vec3d extract_euler_angles(const Transform3d& transform); // get rotation from two vectors. // Default output is axis-angle. If rotation_matrix pointer is provided, also output rotation matrix // Euler angles can be obtained by extract_euler_angles() -void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix = nullptr); - -// Returns the transform obtained by assembling the given translation -Transform3d translation_transform(const Vec3d &translation); - -// Returns the transform obtained by assembling the given rotations in the following order: -// 1) rotate X -// 2) rotate Y -// 3) rotate Z -Transform3d rotation_transform(const Vec3d &rotation); +void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d &rotation_axis, double &phi, Matrix3d *rotation_matrix = nullptr); class Transformation { struct Flags { - bool dont_translate; - bool dont_rotate; - bool dont_scale; - bool dont_mirror; - - Flags(); + bool dont_translate{ true }; + bool dont_rotate{ true }; + bool dont_scale{ true }; + bool dont_mirror{ true }; bool needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const; void set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror); }; - Vec3d m_offset; // In unscaled coordinates - Vec3d m_rotation; // Rotation around the three axes, in radians around mesh center point - Vec3d m_scaling_factor; // Scaling factors along the three axes - Vec3d m_mirror; // Mirroring along the three axes + Vec3d m_offset{ Vec3d::Zero() }; // In unscaled coordinates + Vec3d m_rotation{ Vec3d::Zero() }; // Rotation around the three axes, in radians around mesh center point + Vec3d m_scaling_factor{ Vec3d::Ones() }; // Scaling factors along the three axes + Vec3d m_mirror{ Vec3d::Ones() }; // Mirroring along the three axes - mutable Transform3d m_matrix; + mutable Transform3d m_matrix{ Transform3d::Identity() }; mutable Flags m_flags; - mutable bool m_dirty; + mutable bool m_dirty{ false }; public: Transformation(); @@ -397,6 +440,8 @@ public: const Vec3d& get_rotation() const { return m_rotation; } double get_rotation(Axis axis) const { return m_rotation(axis); } + Transform3d get_rotation_matrix() const; + void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); @@ -457,7 +502,7 @@ extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to // Is the angle close to a multiple of 90 degrees? inline bool is_rotation_ninety_degrees(double a) { - a = fmod(std::abs(a), 0.5 * M_PI); + a = fmod(std::abs(a), 0.5 * PI); if (a > 0.25 * PI) a = 0.5 * PI - a; return a < 0.001; diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp index 4d7c38ccc2..012b240f8a 100644 --- a/src/libslic3r/Geometry/Circle.cpp +++ b/src/libslic3r/Geometry/Circle.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2021 - 2022 Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "Circle.hpp" #include "../Polygon.hpp" @@ -108,7 +112,7 @@ Circled circle_taubin_newton(const Vec2ds& input, size_t cycles) return out; } -Circled circle_ransac(const Vec2ds& input, size_t iterations) +Circled circle_ransac(const Vec2ds& input, size_t iterations, double* min_error) { if (input.size() < 3) return Circled::make_invalid(); @@ -132,6 +136,8 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations) circle_best = c; } } + if (min_error) + *min_error = err_min; return circle_best; } diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp index 39973d916d..a192cc2fd6 100644 --- a/src/libslic3r/Geometry/Circle.hpp +++ b/src/libslic3r/Geometry/Circle.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2021 - 2022 Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_Geometry_Circle_hpp_ #define slic3r_Geometry_Circle_hpp_ @@ -102,7 +106,7 @@ inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20 Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20); // Find circle using RANSAC randomized algorithm. -Circled circle_ransac(const Vec2ds& input, size_t iterations = 20); +Circled circle_ransac(const Vec2ds& input, size_t iterations = 20, double* min_error = nullptr); // Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon. template diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp new file mode 100644 index 0000000000..2e6156a88e --- /dev/null +++ b/src/libslic3r/Measure.cpp @@ -0,0 +1,1255 @@ +///|/ Copyright (c) Prusa Research 2022 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "libslic3r/libslic3r.h" +#include "Measure.hpp" +#include "MeasureUtils.hpp" + +#include "libslic3r/Geometry/Circle.hpp" +#include "libslic3r/SurfaceMesh.hpp" + + +#include +#include + +#define DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE 0 + +namespace Slic3r { +namespace Measure { + + +constexpr double feature_hover_limit = 0.5; // how close to a feature the mouse must be to highlight it + +static std::tuple get_center_and_radius(const std::vector& points, const Transform3d& trafo, const Transform3d& trafo_inv) +{ + Vec2ds out; + double z = 0.; + for (const Vec3d& pt : points) { + Vec3d pt_transformed = trafo * pt; + z = pt_transformed.z(); + out.emplace_back(pt_transformed.x(), pt_transformed.y()); + } + + const int iter = points.size() < 10 ? 2 : + points.size() < 100 ? 4 : + 6; + + double error = std::numeric_limits::max(); + auto circle = Geometry::circle_ransac(out, iter, &error); + + return std::make_tuple(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius, error); +} + + + +static std::array orthonormal_basis(const Vec3d& v) +{ + std::array ret; + ret[2] = v.normalized(); + int index; + ret[2].cwiseAbs().maxCoeff(&index); + switch (index) + { + case 0: { ret[0] = Vec3d(ret[2].y(), -ret[2].x(), 0.0).normalized(); break; } + case 1: { ret[0] = Vec3d(0.0, ret[2].z(), -ret[2].y()).normalized(); break; } + case 2: { ret[0] = Vec3d(-ret[2].z(), 0.0, ret[2].x()).normalized(); break; } + } + ret[1] = ret[2].cross(ret[0]).normalized(); + return ret; +} + + + +class MeasuringImpl { +public: + explicit MeasuringImpl(const indexed_triangle_set& its); + struct PlaneData { + std::vector facets; + std::vector> borders; // FIXME: should be in fact local in update_planes() + std::vector surface_features; + Vec3d normal; + float area; + bool features_extracted = false; + }; + + std::optional get_feature(size_t face_idx, const Vec3d& point); + int get_num_of_planes() const; + const std::vector& get_plane_triangle_indices(int idx) const; + const std::vector& get_plane_features(unsigned int plane_id); + const indexed_triangle_set& get_its() const; + +private: + void update_planes(); + void extract_features(int plane_idx); + + std::vector m_planes; + std::vector m_face_to_plane; + indexed_triangle_set m_its; +}; + + + + + + +MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its) +: m_its(its) +{ + update_planes(); + + // Extracting features will be done as needed. + // To extract all planes at once, run the following: +#if DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE + for (int i=0; i face_normals = its_face_normals(m_its); + const std::vector face_neighbors = its_face_neighbors(m_its); + std::vector facet_queue(num_of_facets, 0); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + size_t seed_facet_idx = 0; + + auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool { + return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); + }; + + m_planes.clear(); + m_planes.reserve(num_of_facets / 5); // empty plane data object is quite lightweight, let's save the initial reallocations + + + // First go through all the triangles and fill in m_planes vector. For each "plane" + // detected on the model, it will contain list of facets that are part of it. + // We will also fill in m_face_to_plane, which contains index into m_planes + // for each of the source facets. + while (1) { + // Find next unvisited triangle: + for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx) + if (m_face_to_plane[seed_facet_idx] == size_t(-1)) { + facet_queue[facet_queue_cnt ++] = seed_facet_idx; + normal_ptr = &face_normals[seed_facet_idx]; + m_face_to_plane[seed_facet_idx] = m_planes.size(); + m_planes.emplace_back(); + break; + } + if (seed_facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = face_normals[facet_idx]; + if (is_same_normal(this_normal, *normal_ptr)) { +// const Vec3i& face = m_its.indices[facet_idx]; + + m_face_to_plane[facet_idx] = m_planes.size() - 1; + m_planes.back().facets.emplace_back(facet_idx); + for (int j = 0; j < 3; ++ j) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && m_face_to_plane[neighbor_idx] == size_t(-1)) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + + m_planes.back().normal = normal_ptr->cast(); + std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end()); + } + + // Check that each facet is part of one of the planes. + assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); + + // Now we will walk around each of the planes and save vertices which form the border. + const SurfaceMesh sm(m_its); + + const auto& face_to_plane = m_face_to_plane; + auto& planes = m_planes; + + tbb::parallel_for(tbb::blocked_range(0, m_planes.size()), + [&planes, &face_to_plane, &face_neighbors, &sm](const tbb::blocked_range& range) { + for (size_t plane_id = range.begin(); plane_id != range.end(); ++plane_id) { + + const auto& facets = planes[plane_id].facets; + planes[plane_id].borders.clear(); + std::vector> visited(facets.size(), {false, false, false}); + + for (int face_id=0; face_id& last_border = planes[plane_id].borders.back(); + last_border.reserve(4); + last_border.emplace_back(sm.point(sm.source(he)).cast()); + //Vertex_index target = sm.target(he); + const Halfedge_index he_start = he; + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + assert(face_it != facets.end()); + assert(*face_it == int(fi)); + visited[face_it - facets.begin()][he.side()] = true; + + do { + const Halfedge_index he_orig = he; + he = sm.next_around_target(he); + if (he.is_invalid()) + goto PLANE_FAILURE; + + // For broken meshes, the iteration might never get back to he_orig. + // Remember all halfedges we saw to break out of such infinite loops. + boost::container::small_vector he_seen; + + while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) { + he_seen.emplace_back(he); + he = sm.next_around_target(he); + if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end()) + goto PLANE_FAILURE; + } + he = sm.opposite(he); + if (he.is_invalid()) + goto PLANE_FAILURE; + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + if (face_it == facets.end() || *face_it != int(fi)) // This indicates a broken mesh. + goto PLANE_FAILURE; + + if (visited[face_it - facets.begin()][he.side()] && he != he_start) { + last_border.resize(1); + break; + } + visited[face_it - facets.begin()][he.side()] = true; + + last_border.emplace_back(sm.point(sm.source(he)).cast()); + + // In case of broken meshes, this loop might be infinite. Break + // out in case it is clearly going bad. + if (last_border.size() > 3*facets.size()+1) + goto PLANE_FAILURE; + + } while (he != he_start); + + if (last_border.size() == 1) + planes[plane_id].borders.pop_back(); + else { + assert(last_border.front() == last_border.back()); + last_border.pop_back(); + } + } + } + continue; // There was no failure. + + PLANE_FAILURE: + planes[plane_id].borders.clear(); + }}); + m_planes.shrink_to_fit(); +} + + + + + + +void MeasuringImpl::extract_features(int plane_idx) +{ + assert(! m_planes[plane_idx].features_extracted); + + PlaneData& plane = m_planes[plane_idx]; + plane.surface_features.clear(); + const Vec3d& normal = plane.normal; + + Eigen::Quaterniond q; + q.setFromTwoVectors(plane.normal, Vec3d::UnitZ()); + Transform3d trafo = Transform3d::Identity(); + trafo.rotate(q); + const Transform3d trafo_inv = trafo.inverse(); + + std::vector angles; // placed in outer scope to prevent reallocations + std::vector lengths; + + for (const std::vector& border : plane.borders) { + if (border.size() <= 1) + continue; + + bool done = false; + + if (border.size() > 4) { + const auto& [center, radius, err] = get_center_and_radius(border, trafo, trafo_inv); + + if (err < 0.05) { + // The whole border is one circle. Just add it into the list of features + // and we are done. + + bool is_polygon = border.size()>4 && border.size()<=8; + bool lengths_match = std::all_of(border.begin()+2, border.end(), [is_polygon](const Vec3d& pt) { + return Slic3r::is_approx((pt - *((&pt)-1)).squaredNorm(), (*((&pt)-1) - *((&pt)-2)).squaredNorm(), is_polygon ? 0.01 : 0.01); + }); + + if (lengths_match && (is_polygon || border.size() > 8)) { + if (is_polygon) { + // This is a polygon, add the separate edges with the center. + for (int j=0; j int { + assert(std::abs(offset) < border_size); + int out = idx+offset; + if (out >= border_size) + out = out - border_size; + else if (out < 0) + out = border_size + out; + + return out; + }; + + // First calculate angles at all the vertices. + angles.clear(); + lengths.clear(); + int first_different_angle_idx = 0; + for (int i=0; i M_PI) + angle = 2*M_PI - angle; + + angles.push_back(angle); + lengths.push_back(v2.norm()); + if (first_different_angle_idx == 0 && angles.size() > 1) { + if (! are_angles_same(angles.back(), angles[angles.size()-2])) + first_different_angle_idx = angles.size()-1; + } + } + assert(border.size() == angles.size()); + assert(border.size() == lengths.size()); + + // First go around the border and pick what might be circular segments. + // Save pair of indices to where such potential segments start and end. + // Also remember the length of these segments. + int start_idx = -1; + bool circle = false; + bool first_iter = true; + std::vector circles; + std::vector edges; + std::vector> circles_idxs; + //std::vector circles_lengths; + std::vector single_circle; // could be in loop-scope, but reallocations + double single_circle_length = 0.; + int first_pt_idx = offset_to_index(first_different_angle_idx, 1); + int i = first_pt_idx; + while (i != first_pt_idx || first_iter) { + if (are_angles_same(angles[i], angles[offset_to_index(i,-1)]) + && i != offset_to_index(first_pt_idx, -1) // not the last point + && i != start_idx ) { + // circle + if (! circle) { + circle = true; + single_circle.clear(); + single_circle_length = 0.; + start_idx = offset_to_index(i, -2); + single_circle = { border[start_idx], border[offset_to_index(start_idx,1)] }; + single_circle_length += lengths[offset_to_index(i, -1)]; + } + single_circle.emplace_back(border[i]); + single_circle_length += lengths[i]; + } else { + if (circle && single_circle.size() >= 5) { // Less than 5 vertices? Not a circle. + single_circle.emplace_back(border[i]); + single_circle_length += lengths[i]; + + bool accept_circle = true; + { + // Check that lengths of internal (!!!) edges match. + int j = offset_to_index(start_idx, 3); + while (j != i) { + if (! are_lengths_same(lengths[offset_to_index(j,-1)], lengths[j])) { + accept_circle = false; + break; + } + j = offset_to_index(j, 1); + } + } + + if (accept_circle) { + const auto& [center, radius, err] = get_center_and_radius(single_circle, trafo, trafo_inv); + + // Check that the fit went well. The tolerance is high, only to + // reject complete failures. + accept_circle &= err < 0.05; + + // If the segment subtends less than 90 degrees, throw it away. + accept_circle &= single_circle_length / radius > 0.9*M_PI/2.; + + if (accept_circle) { + // Add the circle and remember indices into borders. + circles_idxs.emplace_back(start_idx, i); + circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius)); + } + } + } + circle = false; + } + // Take care of the wrap around. + first_iter = false; + i = offset_to_index(i, 1); + } + + // We have the circles. Now go around again and pick edges, while jumping over circles. + if (circles_idxs.empty()) { + // Just add all edges. + for (int i=1; i 1 || circles_idxs.front().first != circles_idxs.front().second) { + // There is at least one circular segment. Start at its end and add edges until the start of the next one. + int i = circles_idxs.front().second; + int circle_idx = 1; + while (true) { + i = offset_to_index(i, 1); + edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[offset_to_index(i,-1)], border[i])); + if (circle_idx < int(circles_idxs.size()) && i == circles_idxs[circle_idx].first) { + i = circles_idxs[circle_idx].second; + ++circle_idx; + } + if (i == circles_idxs.front().first) + break; + } + } + + // Merge adjacent edges where needed. + assert(std::all_of(edges.begin(), edges.end(), + [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; })); + for (int i=edges.size()-1; i>=0; --i) { + const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge(); + const auto& [second_start, second_end] = edges[i].get_edge(); + + if (Slic3r::is_approx(first_end, second_start) + && Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) { + // The edges have the same direction and share a point. Merge them. + edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end); + edges.erase(edges.begin() + i); + } + } + + // Now move the circles and edges into the feature list for the plane. + assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) { + return f.get_type() == SurfaceFeatureType::Circle; + })); + assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) { + return f.get_type() == SurfaceFeatureType::Edge; + })); + plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()), + std::make_move_iterator(circles.end())); + plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()), + std::make_move_iterator(edges.end())); + } + } + + // The last surface feature is the plane itself. + Vec3d cog = Vec3d::Zero(); + size_t counter = 0; + for (const std::vector& b : plane.borders) { + for (size_t i = 1; i < b.size(); ++i) { + cog += b[i]; + ++counter; + } + } + cog /= double(counter); + plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Plane, + plane.normal, cog, std::optional(), plane_idx + 0.0001)); + + plane.borders.clear(); + plane.borders.shrink_to_fit(); + + plane.features_extracted = true; +} + + + + + + + + +std::optional MeasuringImpl::get_feature(size_t face_idx, const Vec3d& point) +{ + if (face_idx >= m_face_to_plane.size()) + return std::optional(); + + const PlaneData& plane = m_planes[m_face_to_plane[face_idx]]; + + if (! plane.features_extracted) + extract_features(m_face_to_plane[face_idx]); + + size_t closest_feature_idx = size_t(-1); + double min_dist = std::numeric_limits::max(); + + MeasurementResult res; + SurfaceFeature point_sf(point); + + assert(plane.surface_features.empty() || plane.surface_features.back().get_type() == SurfaceFeatureType::Plane); + + for (size_t i=0; idist; + if (dist < feature_hover_limit && dist < min_dist) { + min_dist = std::min(dist, min_dist); + closest_feature_idx = i; + } + } + } + + if (closest_feature_idx != size_t(-1)) { + const SurfaceFeature& f = plane.surface_features[closest_feature_idx]; + if (f.get_type() == SurfaceFeatureType::Edge) { + // If this is an edge, check if we are not close to the endpoint. If so, + // we will include the endpoint as well. Close = 10% of the lenghth of + // the edge, clamped between 0.025 and 0.5 mm. + const auto& [sp, ep] = f.get_edge(); + double len_sq = (ep-sp).squaredNorm(); + double limit_sq = std::max(0.025*0.025, std::min(0.5*0.5, 0.1 * 0.1 * len_sq)); + + if ((point-sp).squaredNorm() < limit_sq) + return std::make_optional(SurfaceFeature(sp)); + if ((point-ep).squaredNorm() < limit_sq) + return std::make_optional(SurfaceFeature(ep)); + } + return std::make_optional(f); + } + + // Nothing detected, return the plane as a whole. + assert(plane.surface_features.back().get_type() == SurfaceFeatureType::Plane); + return std::make_optional(plane.surface_features.back()); +} + + + + + +int MeasuringImpl::get_num_of_planes() const +{ + return (m_planes.size()); +} + + + +const std::vector& MeasuringImpl::get_plane_triangle_indices(int idx) const +{ + assert(idx >= 0 && idx < int(m_planes.size())); + return m_planes[idx].facets; +} + +const std::vector& MeasuringImpl::get_plane_features(unsigned int plane_id) +{ + assert(plane_id < m_planes.size()); + if (! m_planes[plane_id].features_extracted) + extract_features(plane_id); + return m_planes[plane_id].surface_features; +} + +const indexed_triangle_set& MeasuringImpl::get_its() const +{ + return this->m_its; +} + + + + + + + + + + + +Measuring::Measuring(const indexed_triangle_set& its) +: priv{std::make_unique(its)} +{} + +Measuring::~Measuring() {} + + + +std::optional Measuring::get_feature(size_t face_idx, const Vec3d& point) const +{ + return priv->get_feature(face_idx, point); +} + + +int Measuring::get_num_of_planes() const +{ + return priv->get_num_of_planes(); +} + + +const std::vector& Measuring::get_plane_triangle_indices(int idx) const +{ + return priv->get_plane_triangle_indices(idx); +} + +const std::vector& Measuring::get_plane_features(unsigned int plane_id) const +{ + return priv->get_plane_features(plane_id); +} + +const indexed_triangle_set& Measuring::get_its() const +{ + return priv->get_its(); +} + +const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true }; + +static AngleAndEdges angle_edge_edge(const std::pair& e1, const std::pair& e2) +{ + if (are_parallel(e1, e2)) + return AngleAndEdges::Dummy; + + Vec3d e1_unit = edge_direction(e1.first, e1.second); + Vec3d e2_unit = edge_direction(e2.first, e2.second); + + // project edges on the plane defined by them + Vec3d normal = e1_unit.cross(e2_unit).normalized(); + const Eigen::Hyperplane plane(normal, e1.first); + Vec3d e11_proj = plane.projection(e1.first); + Vec3d e12_proj = plane.projection(e1.second); + Vec3d e21_proj = plane.projection(e2.first); + Vec3d e22_proj = plane.projection(e2.second); + + const bool coplanar = (e2.first - e21_proj).norm() < EPSILON && (e2.second - e22_proj).norm() < EPSILON; + + // rotate the plane to become the XY plane + auto qp = Eigen::Quaternion::FromTwoVectors(normal, Vec3d::UnitZ()); + auto qp_inverse = qp.inverse(); + const Vec3d e11_rot = qp * e11_proj; + const Vec3d e12_rot = qp * e12_proj; + const Vec3d e21_rot = qp * e21_proj; + const Vec3d e22_rot = qp * e22_proj; + + // discard Z + const Vec2d e11_rot_2d = Vec2d(e11_rot.x(), e11_rot.y()); + const Vec2d e12_rot_2d = Vec2d(e12_rot.x(), e12_rot.y()); + const Vec2d e21_rot_2d = Vec2d(e21_rot.x(), e21_rot.y()); + const Vec2d e22_rot_2d = Vec2d(e22_rot.x(), e22_rot.y()); + + // find intersection (arc center) of edges in XY plane + const Eigen::Hyperplane e1_rot_2d_line = Eigen::Hyperplane::Through(e11_rot_2d, e12_rot_2d); + const Eigen::Hyperplane e2_rot_2d_line = Eigen::Hyperplane::Through(e21_rot_2d, e22_rot_2d); + const Vec2d center_rot_2d = e1_rot_2d_line.intersection(e2_rot_2d_line); + + // arc center in original coordinate + const Vec3d center = qp_inverse * Vec3d(center_rot_2d.x(), center_rot_2d.y(), e11_rot.z()); + + // ensure the edges are pointing away from the center + std::pair out_e1 = e1; + std::pair out_e2 = e2; + if ((center_rot_2d - e11_rot_2d).squaredNorm() > (center_rot_2d - e12_rot_2d).squaredNorm()) { + std::swap(e11_proj, e12_proj); + std::swap(out_e1.first, out_e1.second); + e1_unit = -e1_unit; + } + if ((center_rot_2d - e21_rot_2d).squaredNorm() > (center_rot_2d - e22_rot_2d).squaredNorm()) { + std::swap(e21_proj, e22_proj); + std::swap(out_e2.first, out_e2.second); + e2_unit = -e2_unit; + } + + // arc angle + const double angle = std::acos(std::clamp(e1_unit.dot(e2_unit), -1.0, 1.0)); + // arc radius + const Vec3d e1_proj_mid = 0.5 * (e11_proj + e12_proj); + const Vec3d e2_proj_mid = 0.5 * (e21_proj + e22_proj); + const double radius = std::min((center - e1_proj_mid).norm(), (center - e2_proj_mid).norm()); + + return { angle, center, out_e1, out_e2, radius, coplanar }; +} + +static AngleAndEdges angle_edge_plane(const std::pair& e, const std::tuple& p) +{ + const auto& [idx, normal, origin] = p; + Vec3d e1e2_unit = edge_direction(e); + if (are_perpendicular(e1e2_unit, normal)) + return AngleAndEdges::Dummy; + + // ensure the edge is pointing away from the intersection + // 1st calculate instersection between edge and plane + const Eigen::Hyperplane plane(normal, origin); + const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second); + const Vec3d inters = line.intersectionPoint(plane); + + // then verify edge direction and revert it, if needed + Vec3d e1 = e.first; + Vec3d e2 = e.second; + if ((e1 - inters).squaredNorm() > (e2 - inters).squaredNorm()) { + std::swap(e1, e2); + e1e2_unit = -e1e2_unit; + } + + if (are_parallel(e1e2_unit, normal)) { + const std::array basis = orthonormal_basis(e1e2_unit); + const double radius = (0.5 * (e1 + e2) - inters).norm(); + const Vec3d edge_on_plane_dir = (basis[1].dot(origin - inters) >= 0.0) ? basis[1] : -basis[1]; + std::pair edge_on_plane = std::make_pair(inters, inters + radius * edge_on_plane_dir); + if (!inters.isApprox(e1)) { + edge_on_plane.first += radius * edge_on_plane_dir; + edge_on_plane.second += radius * edge_on_plane_dir; + } + return AngleAndEdges(0.5 * double(PI), inters, std::make_pair(e1, e2), edge_on_plane, radius, inters.isApprox(e1)); + } + + const Vec3d e1e2 = e2 - e1; + const double e1e2_len = e1e2.norm(); + + // calculate 2nd edge (on the plane) + const Vec3d temp = normal.cross(e1e2); + const Vec3d edge_on_plane_unit = normal.cross(temp).normalized(); + std::pair edge_on_plane = { origin, origin + e1e2_len * edge_on_plane_unit }; + + // ensure the 2nd edge is pointing in the correct direction + const Vec3d test_edge = (edge_on_plane.second - edge_on_plane.first).cross(e1e2); + if (test_edge.dot(temp) < 0.0) + edge_on_plane = { origin, origin - e1e2_len * edge_on_plane_unit }; + + AngleAndEdges ret = angle_edge_edge({ e1, e2 }, edge_on_plane); + ret.radius = (inters - 0.5 * (e1 + e2)).norm(); + return ret; +} + +static AngleAndEdges angle_plane_plane(const std::tuple& p1, const std::tuple& p2) +{ + const auto& [idx1, normal1, origin1] = p1; + const auto& [idx2, normal2, origin2] = p2; + + // are planes parallel ? + if (are_parallel(normal1, normal2)) + return AngleAndEdges::Dummy; + + auto intersection_plane_plane = [](const Vec3d& n1, const Vec3d& o1, const Vec3d& n2, const Vec3d& o2) { + Eigen::MatrixXd m(2, 3); + m << n1.x(), n1.y(), n1.z(), n2.x(), n2.y(), n2.z(); + Eigen::VectorXd b(2); + b << o1.dot(n1), o2.dot(n2); + Eigen::VectorXd x = m.colPivHouseholderQr().solve(b); + return std::make_pair(n1.cross(n2).normalized(), Vec3d(x(0), x(1), x(2))); + }; + + // Calculate intersection line between planes + const auto [intersection_line_direction, intersection_line_origin] = intersection_plane_plane(normal1, origin1, normal2, origin2); + + // Project planes' origin on intersection line + const Eigen::ParametrizedLine intersection_line = Eigen::ParametrizedLine(intersection_line_origin, intersection_line_direction); + const Vec3d origin1_proj = intersection_line.projection(origin1); + const Vec3d origin2_proj = intersection_line.projection(origin2); + + // Calculate edges on planes + const Vec3d edge_on_plane1_unit = (origin1 - origin1_proj).normalized(); + const Vec3d edge_on_plane2_unit = (origin2 - origin2_proj).normalized(); + const double radius = std::max(10.0, std::max((origin1 - origin1_proj).norm(), (origin2 - origin2_proj).norm())); + const std::pair edge_on_plane1 = { origin1_proj + radius * edge_on_plane1_unit, origin1_proj + 2.0 * radius * edge_on_plane1_unit }; + const std::pair edge_on_plane2 = { origin2_proj + radius * edge_on_plane2_unit, origin2_proj + 2.0 * radius * edge_on_plane2_unit }; + + AngleAndEdges ret = angle_edge_edge(edge_on_plane1, edge_on_plane2); + ret.radius = radius; + return ret; +} + + + + + + + +MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring) +{ + assert(a.get_type() != SurfaceFeatureType::Undef && b.get_type() != SurfaceFeatureType::Undef); + + const bool swap = int(a.get_type()) > int(b.get_type()); + const SurfaceFeature& f1 = swap ? b : a; + const SurfaceFeature& f2 = swap ? a : b; + + MeasurementResult result; + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + if (f1.get_type() == SurfaceFeatureType::Point) { + if (f2.get_type() == SurfaceFeatureType::Point) { + Vec3d diff = (f2.get_point() - f1.get_point()); + result.distance_strict = std::make_optional(DistAndPoints{diff.norm(), f1.get_point(), f2.get_point()}); + result.distance_xyz = diff.cwiseAbs(); + + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Edge) { + const auto [s,e] = f2.get_edge(); + const Eigen::ParametrizedLine line(s, (e-s).normalized()); + const double dist_inf = line.distance(f1.get_point()); + const Vec3d proj = line.projection(f1.get_point()); + const double len_sq = (e-s).squaredNorm(); + const double dist_start_sq = (proj-s).squaredNorm(); + const double dist_end_sq = (proj-e).squaredNorm(); + if (dist_start_sq < len_sq && dist_end_sq < len_sq) { + // projection falls on the line - the strict distance is the same as infinite + result.distance_strict = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj}); + } else { // the result is the closer of the endpoints + const bool s_is_closer = dist_start_sq < dist_end_sq; + result.distance_strict = std::make_optional(DistAndPoints{std::sqrt(std::min(dist_start_sq, dist_end_sq) + sqr(dist_inf)), f1.get_point(), s_is_closer ? s : e}); + } + result.distance_infinite = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj}); + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Circle) { + // Find a plane containing normal, center and the point. + const auto [c, radius, n] = f2.get_circle(); + const Eigen::Hyperplane circle_plane(n, c); + const Vec3d proj = circle_plane.projection(f1.get_point()); + if (proj.isApprox(c)) { + const Vec3d p_on_circle = c + radius * get_orthogonal(n, true); + result.distance_strict = std::make_optional(DistAndPoints{ radius, c, p_on_circle }); + } + else { + const Eigen::Hyperplane circle_plane(n, c); + const Vec3d proj = circle_plane.projection(f1.get_point()); + const double dist = std::sqrt(std::pow((proj - c).norm() - radius, 2.) + + (f1.get_point() - proj).squaredNorm()); + + const Vec3d p_on_circle = c + radius * (proj - c).normalized(); + result.distance_strict = std::make_optional(DistAndPoints{ dist, f1.get_point(), p_on_circle }); // TODO + } + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Plane) { + const auto [idx, normal, pt] = f2.get_plane(); + Eigen::Hyperplane plane(normal, pt); + result.distance_infinite = std::make_optional(DistAndPoints{plane.absDistance(f1.get_point()), f1.get_point(), plane.projection(f1.get_point())}); // TODO + // TODO: result.distance_strict = + } + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + } + else if (f1.get_type() == SurfaceFeatureType::Edge) { + if (f2.get_type() == SurfaceFeatureType::Edge) { + std::vector distances; + + auto add_point_edge_distance = [&distances](const Vec3d& v, const std::pair& e) { + const MeasurementResult res = get_measurement(SurfaceFeature(v), SurfaceFeature(SurfaceFeatureType::Edge, e.first, e.second)); + double distance = res.distance_strict->dist; + Vec3d v2 = res.distance_strict->to; + + const Vec3d e1e2 = e.second - e.first; + const Vec3d e1v2 = v2 - e.first; + if (e1v2.dot(e1e2) >= 0.0 && e1v2.norm() < e1e2.norm()) + distances.emplace_back(distance, v, v2); + }; + + std::pair e1 = f1.get_edge(); + std::pair e2 = f2.get_edge(); + + distances.emplace_back((e2.first - e1.first).norm(), e1.first, e2.first); + distances.emplace_back((e2.second - e1.first).norm(), e1.first, e2.second); + distances.emplace_back((e2.first - e1.second).norm(), e1.second, e2.first); + distances.emplace_back((e2.second - e1.second).norm(), e1.second, e2.second); + add_point_edge_distance(e1.first, e2); + add_point_edge_distance(e1.second, e2); + add_point_edge_distance(e2.first, e1); + add_point_edge_distance(e2.second, e1); + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(*it); + + result.angle = angle_edge_edge(f1.get_edge(), f2.get_edge()); + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Circle) { + const std::pair e = f1.get_edge(); + const auto& [center, radius, normal] = f2.get_circle(); + const Vec3d e1e2 = (e.second - e.first); + const Vec3d e1e2_unit = e1e2.normalized(); + + std::vector distances; + distances.emplace_back(*get_measurement(SurfaceFeature(e.first), f2).distance_strict); + distances.emplace_back(*get_measurement(SurfaceFeature(e.second), f2).distance_strict); + + const Eigen::Hyperplane plane(e1e2_unit, center); + const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second); + const Vec3d inter = line.intersectionPoint(plane); + const Vec3d e1inter = inter - e.first; + if (e1inter.dot(e1e2) >= 0.0 && e1inter.norm() < e1e2.norm()) + distances.emplace_back(*get_measurement(SurfaceFeature(inter), f2).distance_strict); + + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{it->dist, it->from, it->to}); + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Plane) { + assert(measuring != nullptr); + + const auto [from, to] = f1.get_edge(); + const auto [idx, normal, origin] = f2.get_plane(); + + const Vec3d edge_unit = (to - from).normalized(); + if (are_perpendicular(edge_unit, normal)) { + std::vector distances; + const Eigen::Hyperplane plane(normal, origin); + distances.push_back(DistAndPoints{ plane.absDistance(from), from, plane.projection(from) }); + distances.push_back(DistAndPoints{ plane.absDistance(to), to, plane.projection(to) }); + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to }); + } + else { + const std::vector& plane_features = measuring->get_plane_features(idx); + std::vector distances; + for (const SurfaceFeature& sf : plane_features) { + if (sf.get_type() == SurfaceFeatureType::Edge) { + const auto m = get_measurement(sf, f1); + if (!m.distance_infinite.has_value()) { + distances.clear(); + break; + } + else + distances.push_back(*m.distance_infinite); + } + } + if (!distances.empty()) { + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to }); + } + } + result.angle = angle_edge_plane(f1.get_edge(), f2.get_plane()); + } + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + } else if (f1.get_type() == SurfaceFeatureType::Circle) { + if (f2.get_type() == SurfaceFeatureType::Circle) { + const auto [c0, r0, n0] = f1.get_circle(); + const auto [c1, r1, n1] = f2.get_circle(); + + // The following code is an adaptation of the algorithm found in: + // https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/DistCircle3Circle3.h + // and described in: + // https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf + + struct ClosestInfo + { + double sqrDistance{ 0.0 }; + Vec3d circle0Closest{ Vec3d::Zero() }; + Vec3d circle1Closest{ Vec3d::Zero() }; + + inline bool operator < (const ClosestInfo& other) const { return sqrDistance < other.sqrDistance; } + }; + std::array candidates{}; + + const double zero = 0.0; + + const Vec3d D = c1 - c0; + + if (!are_parallel(n0, n1)) { + // Get parameters for constructing the degree-8 polynomial phi. + const double one = 1.0; + const double two = 2.0; + const double r0sqr = sqr(r0); + const double r1sqr = sqr(r1); + + // Compute U1 and V1 for the plane of circle1. + const std::array basis = orthonormal_basis(n1); + const Vec3d U1 = basis[0]; + const Vec3d V1 = basis[1]; + + // Construct the polynomial phi(cos(theta)). + const Vec3d N0xD = n0.cross(D); + const Vec3d N0xU1 = n0.cross(U1); + const Vec3d N0xV1 = n0.cross(V1); + const double a0 = r1 * D.dot(U1); + const double a1 = r1 * D.dot(V1); + const double a2 = N0xD.dot(N0xD); + const double a3 = r1 * N0xD.dot(N0xU1); + const double a4 = r1 * N0xD.dot(N0xV1); + const double a5 = r1sqr * N0xU1.dot(N0xU1); + const double a6 = r1sqr * N0xU1.dot(N0xV1); + const double a7 = r1sqr * N0xV1.dot(N0xV1); + Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 }; + Polynomial1 p1{ two * a4, two * a6 }; + Polynomial1 p2{ zero, a1 }; + Polynomial1 p3{ -a0 }; + Polynomial1 p4{ -a6, a4, two * a6 }; + Polynomial1 p5{ -a3, a7 - a5 }; + Polynomial1 tmp0{ one, zero, -one }; + Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3; + Polynomial1 tmp2 = two * p2 * p3; + Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5; + Polynomial1 tmp4 = two * p4 * p5; + Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3; + Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4; + + // Parameters for polynomial root finding. The roots[] array + // stores the roots. We need only the unique ones, which is + // the responsibility of the set uniqueRoots. The pairs[] + // array stores the (cosine,sine) information mentioned in the + // PDF. TODO: Choose the maximum number of iterations for root + // finding based on specific polynomial data? + const uint32_t maxIterations = 128; + int32_t degree = 0; + size_t numRoots = 0; + std::array roots{}; + std::set uniqueRoots{}; + size_t numPairs = 0; + std::array, 16> pairs{}; + double temp = zero; + double sn = zero; + + if (p7.GetDegree() > 0 || p7[0] != zero) { + // H(cs,sn) = p6(cs) + sn * p7(cs) + Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7; + degree = static_cast(phi.GetDegree()); + assert(degree > 0); + numRoots = RootsPolynomial::Find(degree, &phi[0], maxIterations, roots.data()); + for (size_t i = 0; i < numRoots; ++i) { + uniqueRoots.insert(roots[i]); + } + + for (auto const& cs : uniqueRoots) { + if (std::fabs(cs) <= one) { + temp = p7(cs); + if (temp != zero) { + sn = -p6(cs) / temp; + pairs[numPairs++] = std::make_pair(cs, sn); + } + else { + temp = std::max(one - sqr(cs), zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + else { + // H(cs,sn) = p6(cs) + degree = static_cast(p6.GetDegree()); + assert(degree > 0); + numRoots = RootsPolynomial::Find(degree, &p6[0], maxIterations, roots.data()); + for (size_t i = 0; i < numRoots; ++i) { + uniqueRoots.insert(roots[i]); + } + + for (auto const& cs : uniqueRoots) { + if (std::fabs(cs) <= one) { + temp = std::max(one - sqr(cs), zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + + for (size_t i = 0; i < numPairs; ++i) { + ClosestInfo& info = candidates[i]; + Vec3d delta = D + r1 * (pairs[i].first * U1 + pairs[i].second * V1); + info.circle1Closest = c0 + delta; + const double N0dDelta = n0.dot(delta); + const double lenN0xDelta = n0.cross(delta).norm(); + if (lenN0xDelta > 0.0) { + const double diff = lenN0xDelta - r0; + info.sqrDistance = sqr(N0dDelta) + sqr(diff); + delta -= N0dDelta * n0; + delta.normalize(); + info.circle0Closest = c0 + r0 * delta; + } + else { + const Vec3d r0U0 = r0 * get_orthogonal(n0, true); + const Vec3d diff = delta - r0U0; + info.sqrDistance = diff.dot(diff); + info.circle0Closest = c0 + r0U0; + } + } + + std::sort(candidates.begin(), candidates.begin() + numPairs); + } + else { + ClosestInfo& info = candidates[0]; + + const double N0dD = n0.dot(D); + const Vec3d normProj = N0dD * n0; + const Vec3d compProj = D - normProj; + Vec3d U = compProj; + const double d = U.norm(); + U.normalize(); + + // The configuration is determined by the relative location of the + // intervals of projection of the circles on to the D-line. + // Circle0 projects to [-r0,r0] and circle1 projects to + // [d-r1,d+r1]. + const double dmr1 = d - r1; + double distance; + if (dmr1 >= r0) { + // d >= r0 + r1 + // The circles are separated (d > r0 + r1) or tangent with one + // outside the other (d = r0 + r1). + distance = dmr1 - r0; + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 - r1 * U; + } + else { + // d < r0 + r1 + // The cases implicitly use the knowledge that d >= 0. + const double dpr1 = d + r1; + if (dpr1 <= r0) { + // Circle1 is inside circle0. + distance = r0 - dpr1; + if (d > 0.0) { + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 + r1 * U; + } + else { + // The circles are concentric, so U = (0,0,0). + // Construct a vector perpendicular to N0 to use for + // closest points. + U = get_orthogonal(n0, true); + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 + r1 * U; + } + } + else if (dmr1 <= -r0) { + // Circle0 is inside circle1. + distance = -r0 - dmr1; + if (d > 0.0) { + info.circle0Closest = c0 - r0 * U; + info.circle1Closest = c1 - r1 * U; + } + else { + // The circles are concentric, so U = (0,0,0). + // Construct a vector perpendicular to N0 to use for + // closest points. + U = get_orthogonal(n0, true); + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 + r1 * U; + } + } + else { + distance = (c1 - c0).norm(); + info.circle0Closest = c0; + info.circle1Closest = c1; + } + } + + info.sqrDistance = distance * distance + N0dD * N0dD; + } + + result.distance_infinite = std::make_optional(DistAndPoints{ std::sqrt(candidates[0].sqrDistance), candidates[0].circle0Closest, candidates[0].circle1Closest }); // TODO + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Plane) { + assert(measuring != nullptr); + + const auto [center, radius, normal1] = f1.get_circle(); + const auto [idx2, normal2, origin2] = f2.get_plane(); + + const bool coplanar = are_parallel(normal1, normal2) && Eigen::Hyperplane(normal1, center).absDistance(origin2) < EPSILON; + if (!coplanar) { + const std::vector& plane_features = measuring->get_plane_features(idx2); + std::vector distances; + for (const SurfaceFeature& sf : plane_features) { + if (sf.get_type() == SurfaceFeatureType::Edge) { + const auto m = get_measurement(sf, f1); + if (!m.distance_infinite.has_value()) { + distances.clear(); + break; + } + else + distances.push_back(*m.distance_infinite); + } + } + if (!distances.empty()) { + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to }); + } + } + } + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + } else if (f1.get_type() == SurfaceFeatureType::Plane) { + const auto [idx1, normal1, pt1] = f1.get_plane(); + const auto [idx2, normal2, pt2] = f2.get_plane(); + + if (are_parallel(normal1, normal2)) { + // The planes are parallel, calculate distance. + const Eigen::Hyperplane plane(normal1, pt1); + result.distance_infinite = std::make_optional(DistAndPoints{ plane.absDistance(pt2), pt2, plane.projection(pt2) }); // TODO + } + else + result.angle = angle_plane_plane(f1.get_plane(), f2.get_plane()); + } + + return result; +} + + + + + + + + +} // namespace Measure +} // namespace Slic3r + diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp new file mode 100644 index 0000000000..dcccafb70d --- /dev/null +++ b/src/libslic3r/Measure.hpp @@ -0,0 +1,200 @@ +///|/ Copyright (c) Prusa Research 2022 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef Slic3r_Measure_hpp_ +#define Slic3r_Measure_hpp_ + +#include +#include + +#include "Point.hpp" + + +struct indexed_triangle_set; + + + +namespace Slic3r { + +class TriangleMesh; + +namespace Measure { + + +enum class SurfaceFeatureType : int { + Undef = 0, + Point = 1 << 0, + Edge = 1 << 1, + Circle = 1 << 2, + Plane = 1 << 3 +}; + +class SurfaceFeature { +public: + SurfaceFeature(SurfaceFeatureType type, const Vec3d& pt1, const Vec3d& pt2, std::optional pt3 = std::nullopt, double value = 0.0) + : m_type(type), m_pt1(pt1), m_pt2(pt2), m_pt3(pt3), m_value(value) {} + + explicit SurfaceFeature(const Vec3d& pt) + : m_type{SurfaceFeatureType::Point}, m_pt1{pt} {} + + // Get type of this feature. + SurfaceFeatureType get_type() const { return m_type; } + + // For points, return the point. + Vec3d get_point() const { assert(m_type == SurfaceFeatureType::Point); return m_pt1; } + + // For edges, return start and end. + std::pair get_edge() const { assert(m_type == SurfaceFeatureType::Edge); return std::make_pair(m_pt1, m_pt2); } + + // For circles, return center, radius and normal. + std::tuple get_circle() const { assert(m_type == SurfaceFeatureType::Circle); return std::make_tuple(m_pt1, m_value, m_pt2); } + + // For planes, return index into vector provided by Measuring::get_plane_triangle_indices, normal and point. + std::tuple get_plane() const { assert(m_type == SurfaceFeatureType::Plane); return std::make_tuple(int(m_value), m_pt1, m_pt2); } + + // For anything, return an extra point that should also be considered a part of this. + std::optional get_extra_point() const { assert(m_type != SurfaceFeatureType::Undef); return m_pt3; } + + bool operator == (const SurfaceFeature& other) const { + if (this->m_type != other.m_type) return false; + switch (this->m_type) + { + case SurfaceFeatureType::Undef: { break; } + case SurfaceFeatureType::Point: { return (this->m_pt1.isApprox(other.m_pt1)); } + case SurfaceFeatureType::Edge: { + return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2)) || + (this->m_pt1.isApprox(other.m_pt2) && this->m_pt2.isApprox(other.m_pt1)); + } + case SurfaceFeatureType::Plane: + case SurfaceFeatureType::Circle: { + return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2) && std::abs(this->m_value - other.m_value) < EPSILON); + } + } + + return false; + } + + bool operator != (const SurfaceFeature& other) const { + return !operator == (other); + } + +private: + SurfaceFeatureType m_type{ SurfaceFeatureType::Undef }; + Vec3d m_pt1{ Vec3d::Zero() }; + Vec3d m_pt2{ Vec3d::Zero() }; + std::optional m_pt3; + double m_value{ 0.0 }; +}; + + + +class MeasuringImpl; + + +class Measuring { +public: + // Construct the measurement object on a given its. + explicit Measuring(const indexed_triangle_set& its); + ~Measuring(); + + + // Given a face_idx where the mouse cursor points, return a feature that + // should be highlighted (if any). + std::optional get_feature(size_t face_idx, const Vec3d& point) const; + + // Return total number of planes. + int get_num_of_planes() const; + + // Returns a list of triangle indices for given plane. + const std::vector& get_plane_triangle_indices(int idx) const; + + // Returns the surface features of the plane with the given index + const std::vector& get_plane_features(unsigned int plane_id) const; + + // Returns the mesh used for measuring + const indexed_triangle_set& get_its() const; + +private: + std::unique_ptr priv; +}; + + +struct DistAndPoints { + DistAndPoints(double dist_, Vec3d from_, Vec3d to_) : dist(dist_), from(from_), to(to_) {} + double dist; + Vec3d from; + Vec3d to; +}; + +struct AngleAndEdges { + AngleAndEdges(double angle_, const Vec3d& center_, const std::pair& e1_, const std::pair& e2_, double radius_, bool coplanar_) + : angle(angle_), center(center_), e1(e1_), e2(e2_), radius(radius_), coplanar(coplanar_) {} + double angle; + Vec3d center; + std::pair e1; + std::pair e2; + double radius; + bool coplanar; + + static const AngleAndEdges Dummy; +}; + +struct MeasurementResult { + std::optional angle; + std::optional distance_infinite; + std::optional distance_strict; + std::optional distance_xyz; + + bool has_distance_data() const { + return distance_infinite.has_value() || distance_strict.has_value(); + } + + bool has_any_data() const { + return angle.has_value() || distance_infinite.has_value() || distance_strict.has_value() || distance_xyz.has_value(); + } +}; + +// Returns distance/angle between two SurfaceFeatures. +MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring = nullptr); + +inline Vec3d edge_direction(const Vec3d& from, const Vec3d& to) { return (to - from).normalized(); } +inline Vec3d edge_direction(const std::pair& e) { return edge_direction(e.first, e.second); } +inline Vec3d edge_direction(const SurfaceFeature& edge) { + assert(edge.get_type() == SurfaceFeatureType::Edge); + return edge_direction(edge.get_edge()); +} + +inline Vec3d plane_normal(const SurfaceFeature& plane) { + assert(plane.get_type() == SurfaceFeatureType::Plane); + return std::get<1>(plane.get_plane()); +} + +inline bool are_parallel(const Vec3d& v1, const Vec3d& v2) { return std::abs(std::abs(v1.dot(v2)) - 1.0) < EPSILON; } +inline bool are_perpendicular(const Vec3d& v1, const Vec3d& v2) { return std::abs(v1.dot(v2)) < EPSILON; } + +inline bool are_parallel(const std::pair& e1, const std::pair& e2) { + return are_parallel(e1.second - e1.first, e2.second - e2.first); +} +inline bool are_parallel(const SurfaceFeature& f1, const SurfaceFeature& f2) { + if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge) + return are_parallel(edge_direction(f1), edge_direction(f2)); + else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane) + return are_perpendicular(edge_direction(f1), plane_normal(f2)); + else + return false; +} + +inline bool are_perpendicular(const SurfaceFeature& f1, const SurfaceFeature& f2) { + if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge) + return are_perpendicular(edge_direction(f1), edge_direction(f2)); + else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane) + return are_parallel(edge_direction(f1), plane_normal(f2)); + else + return false; +} + +} // namespace Measure +} // namespace Slic3r + +#endif // Slic3r_Measure_hpp_ diff --git a/src/libslic3r/MeasureUtils.hpp b/src/libslic3r/MeasureUtils.hpp new file mode 100644 index 0000000000..8a63de5a1f --- /dev/null +++ b/src/libslic3r/MeasureUtils.hpp @@ -0,0 +1,390 @@ +///|/ Copyright (c) Prusa Research 2022 Enrico Turri @enricoturri1966 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef Slic3r_MeasureUtils_hpp_ +#define Slic3r_MeasureUtils_hpp_ + +#include + +namespace Slic3r { +namespace Measure { + +// Utility class used to calculate distance circle-circle +// Adaptation of code found in: +// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Polynomial1.h + +class Polynomial1 +{ +public: + Polynomial1(std::initializer_list values) + { + // C++ 11 will call the default constructor for + // Polynomial1 p{}, so it is guaranteed that + // values.size() > 0. + m_coefficient.resize(values.size()); + std::copy(values.begin(), values.end(), m_coefficient.begin()); + EliminateLeadingZeros(); + } + + // Construction and destruction. The first constructor creates a + // polynomial of the specified degree but sets all coefficients to + // zero (to ensure initialization). You are responsible for setting + // the coefficients, presumably with the degree-term set to a nonzero + // number. In the second constructor, the degree is the number of + // initializers plus 1, but then adjusted so that coefficient[degree] + // is not zero (unless all initializer values are zero). + explicit Polynomial1(uint32_t degree) + : m_coefficient(static_cast(degree) + 1, 0.0) + {} + + // Eliminate any leading zeros in the polynomial, except in the case + // the degree is 0 and the coefficient is 0. The elimination is + // necessary when arithmetic operations cause a decrease in the degree + // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) = + // (2 + 3*x). The inputs both have degree 2, so the result is created + // with degree 2. After the addition we find that the degree is in + // fact 1 and resize the array of coefficients. This function is + // called internally by the arithmetic operators, but it is exposed in + // the public interface in case you need it for your own purposes. + void EliminateLeadingZeros() + { + const size_t size = m_coefficient.size(); + if (size > 1) { + const double zero = 0.0; + int32_t leading; + for (leading = static_cast(size) - 1; leading > 0; --leading) { + if (m_coefficient[leading] != zero) + break; + } + + m_coefficient.resize(++leading); + } + } + + // Set all coefficients to the specified value. + void SetCoefficients(double value) + { + std::fill(m_coefficient.begin(), m_coefficient.end(), value); + } + + inline uint32_t GetDegree() const + { + // By design, m_coefficient.size() > 0. + return static_cast(m_coefficient.size() - 1); + } + + inline const double& operator[](uint32_t i) const { return m_coefficient[i]; } + inline double& operator[](uint32_t i) { return m_coefficient[i]; } + + // Evaluate the polynomial. If the polynomial is invalid, the + // function returns zero. + double operator()(double t) const + { + int32_t i = static_cast(m_coefficient.size()); + double result = m_coefficient[--i]; + for (--i; i >= 0; --i) { + result *= t; + result += m_coefficient[i]; + } + return result; + } + +protected: + // The class is designed so that m_coefficient.size() >= 1. + std::vector m_coefficient; +}; + +inline Polynomial1 operator * (const Polynomial1& p0, const Polynomial1& p1) +{ + const uint32_t p0Degree = p0.GetDegree(); + const uint32_t p1Degree = p1.GetDegree(); + Polynomial1 result(p0Degree + p1Degree); + result.SetCoefficients(0.0); + for (uint32_t i0 = 0; i0 <= p0Degree; ++i0) { + for (uint32_t i1 = 0; i1 <= p1Degree; ++i1) { + result[i0 + i1] += p0[i0] * p1[i1]; + } + } + return result; +} + +inline Polynomial1 operator + (const Polynomial1& p0, const Polynomial1& p1) +{ + const uint32_t p0Degree = p0.GetDegree(); + const uint32_t p1Degree = p1.GetDegree(); + uint32_t i; + if (p0Degree >= p1Degree) { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p0Degree; ++i) { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p1Degree; ++i) { + result[i] = p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } +} + +inline Polynomial1 operator - (const Polynomial1& p0, const Polynomial1& p1) +{ + const uint32_t p0Degree = p0.GetDegree(); + const uint32_t p1Degree = p1.GetDegree(); + uint32_t i; + if (p0Degree >= p1Degree) { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p0Degree; ++i) { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p1Degree; ++i) { + result[i] = -p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } +} + +inline Polynomial1 operator * (double scalar, const Polynomial1& p) +{ + const uint32_t degree = p.GetDegree(); + Polynomial1 result(degree); + for (uint32_t i = 0; i <= degree; ++i) { + result[i] = scalar * p[i]; + } + return result; +} + +// Utility class used to calculate distance circle-circle +// Adaptation of code found in: +// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/RootsPolynomial.h + +class RootsPolynomial +{ +public: + // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c' + // must have at least d+1 elements and the output array 'root' must + // have at least d elements. + + // Find the roots on (-infinity,+infinity). + static int32_t Find(int32_t degree, const double* c, uint32_t maxIterations, double* roots) + { + if (degree >= 0 && c != nullptr) { + const double zero = 0.0; + while (degree >= 0 && c[degree] == zero) { + --degree; + } + + if (degree > 0) { + // Compute the Cauchy bound. + const double one = 1.0; + const double invLeading = one / c[degree]; + double maxValue = zero; + for (int32_t i = 0; i < degree; ++i) { + const double value = std::fabs(c[i] * invLeading); + if (value > maxValue) + maxValue = value; + } + const double bound = one + maxValue; + + return FindRecursive(degree, c, -bound, bound, maxIterations, roots); + } + else if (degree == 0) + // The polynomial is a nonzero constant. + return 0; + else { + // The polynomial is identically zero. + roots[0] = zero; + return 1; + } + } + else + // Invalid degree or c. + return 0; + } + + // If you know that p(tmin) * p(tmax) <= 0, then there must be at + // least one root in [tmin, tmax]. Compute it using bisection. + static bool Find(int32_t degree, const double* c, double tmin, double tmax, uint32_t maxIterations, double& root) + { + const double zero = 0.0; + double pmin = Evaluate(degree, c, tmin); + if (pmin == zero) { + root = tmin; + return true; + } + double pmax = Evaluate(degree, c, tmax); + if (pmax == zero) { + root = tmax; + return true; + } + + if (pmin * pmax > zero) + // It is not known whether the interval bounds a root. + return false; + + if (tmin >= tmax) + // Invalid ordering of interval endpoitns. + return false; + + for (uint32_t i = 1; i <= maxIterations; ++i) { + root = 0.5 * (tmin + tmax); + + // This test is designed for 'float' or 'double' when tmin + // and tmax are consecutive floating-point numbers. + if (root == tmin || root == tmax) + break; + + const double p = Evaluate(degree, c, root); + const double product = p * pmin; + if (product < zero) { + tmax = root; + pmax = p; + } + else if (product > zero) { + tmin = root; + pmin = p; + } + else + break; + } + + return true; + } + + // Support for the Find functions. + static int32_t FindRecursive(int32_t degree, double const* c, double tmin, double tmax, uint32_t maxIterations, double* roots) + { + // The base of the recursion. + const double zero = 0.0; + double root = zero; + if (degree == 1) { + int32_t numRoots; + if (c[1] != zero) { + root = -c[0] / c[1]; + numRoots = 1; + } + else if (c[0] == zero) { + root = zero; + numRoots = 1; + } + else + numRoots = 0; + + if (numRoots > 0 && tmin <= root && root <= tmax) { + roots[0] = root; + return 1; + } + return 0; + } + + // Find the roots of the derivative polynomial scaled by 1/degree. + // The scaling avoids the factorial growth in the coefficients; + // for example, without the scaling, the high-order term x^d + // becomes (d!)*x through multiple differentiations. With the + // scaling we instead get x. This leads to better numerical + // behavior of the root finder. + const int32_t derivDegree = degree - 1; + std::vector derivCoeff(static_cast(derivDegree) + 1); + std::vector derivRoots(derivDegree); + for (int32_t i = 0, ip1 = 1; i <= derivDegree; ++i, ++ip1) { + derivCoeff[i] = c[ip1] * (double)(ip1) / (double)degree; + } + const int32_t numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, maxIterations, &derivRoots[0]); + + int32_t numRoots = 0; + if (numDerivRoots > 0) { + // Find root on [tmin,derivRoots[0]]. + if (Find(degree, c, tmin, derivRoots[0], maxIterations, root)) + roots[numRoots++] = root; + + // Find root on [derivRoots[i],derivRoots[i+1]]. + for (int32_t i = 0, ip1 = 1; i <= numDerivRoots - 2; ++i, ++ip1) { + if (Find(degree, c, derivRoots[i], derivRoots[ip1], maxIterations, root)) + roots[numRoots++] = root; + } + + // Find root on [derivRoots[numDerivRoots-1],tmax]. + if (Find(degree, c, derivRoots[static_cast(numDerivRoots) - 1], tmax, maxIterations, root)) + roots[numRoots++] = root; + } + else { + // The polynomial is monotone on [tmin,tmax], so has at most one root. + if (Find(degree, c, tmin, tmax, maxIterations, root)) + roots[numRoots++] = root; + } + return numRoots; + } + + static double Evaluate(int32_t degree, const double* c, double t) + { + int32_t i = degree; + double result = c[i]; + while (--i >= 0) { + result = t * result + c[i]; + } + return result; + } +}; + +// Adaptation of code found in: +// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Vector.h + +// Construct a single vector orthogonal to the nonzero input vector. If +// the maximum absolute component occurs at index i, then the orthogonal +// vector U has u[i] = v[i+1], u[i+1] = -v[i], and all other components +// zero. The index addition i+1 is computed modulo N. +inline Vec3d get_orthogonal(const Vec3d& v, bool unitLength) +{ + double cmax = std::fabs(v[0]); + int32_t imax = 0; + for (int32_t i = 1; i < 3; ++i) { + double c = std::fabs(v[i]); + if (c > cmax) { + cmax = c; + imax = i; + } + } + + Vec3d result = Vec3d::Zero(); + int32_t inext = imax + 1; + if (inext == 3) + inext = 0; + + result[imax] = v[inext]; + result[inext] = -v[imax]; + if (unitLength) { + const double sqrDistance = result[imax] * result[imax] + result[inext] * result[inext]; + const double invLength = 1.0 / std::sqrt(sqrDistance); + result[imax] *= invLength; + result[inext] *= invLength; + } + return result; +} + +} // namespace Slic3r +} // namespace Measure + +#endif // Slic3r_MeasureUtils_hpp_ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 90879d49c2..1cd76fc2c5 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,3 +1,16 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2021 Boleslaw Ciesielski +///|/ Copyright (c) 2019 John Drake @foxox +///|/ Copyright (c) 2019 Sijmen Schoon +///|/ Copyright (c) Slic3r 2014 - 2016 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard +///|/ +///|/ ported from lib/Slic3r/Model.pm: +///|/ Copyright (c) Prusa Research 2016 - 2022 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966 +///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "Model.hpp" #include "libslic3r.h" #include "BuildVolume.hpp" @@ -1693,68 +1706,6 @@ bool ModelObject::has_connectors() const return false; } -indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) -{ - indexed_triangle_set connector_mesh; - - int sectorCount {1}; - switch (CutConnectorShape(connector_attributes.shape)) { - case CutConnectorShape::Triangle: - sectorCount = 3; - break; - case CutConnectorShape::Square: - sectorCount = 4; - break; - case CutConnectorShape::Circle: - sectorCount = 360; - break; - case CutConnectorShape::Hexagon: - sectorCount = 6; - break; - default: - break; - } - - if (connector_attributes.style == CutConnectorStyle::Prizm) - connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); - else if (connector_attributes.type == CutConnectorType::Plug) - connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount)); - else - connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); - - return connector_mesh; -} - -void ModelObject::apply_cut_connectors(const std::string &name) -{ - if (cut_connectors.empty()) - return; - - using namespace Geometry; - - size_t connector_id = cut_id.connectors_cnt(); - for (const CutConnector &connector : cut_connectors) { - TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); - // Mesh will be centered when loading. - ModelVolume *new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); - - Transform3d translate_transform = Transform3d::Identity(); - translate_transform.translate(connector.pos); - Transform3d scale_transform = Transform3d::Identity(); - scale_transform.scale(Vec3f(connector.radius, connector.radius, connector.height).cast()); - - // Transform the new modifier to be aligned inside the instance - new_volume->set_transformation(translate_transform * connector.rotation_m * scale_transform); - - new_volume->cut_info = {connector.attribs.type, connector.radius, connector.height, connector.radius_tolerance, connector.height_tolerance}; - new_volume->name = name + "-" + std::to_string(++connector_id); - } - cut_id.increase_connectors_cnt(cut_connectors.size()); - - // delete all connectors - cut_connectors.clear(); -} - void ModelObject::invalidate_cut() { this->cut_id.invalidate(); @@ -1770,43 +1721,10 @@ void ModelObject::delete_connectors() } } -void ModelObject::synchronize_model_after_cut() -{ - for (ModelObject *obj : m_model->objects) { - if (obj == this || obj->cut_id.is_equal(this->cut_id)) continue; - if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id)) - obj->cut_id.copy(this->cut_id); - } -} - -void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes) -{ - // we don't save cut information, if result will not contains all parts of initial object - if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || - !attributes.has(ModelObjectCutAttribute::KeepLower) || - attributes.has(ModelObjectCutAttribute::InvalidateCutInfo)) - return; - - if (cut_id.id().invalid()) - cut_id.init(); - - { - int cut_obj_cnt = -1; - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - cut_obj_cnt++; - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - cut_obj_cnt++; - if (attributes.has(ModelObjectCutAttribute::CreateDowels)) - cut_obj_cnt++; - if (cut_obj_cnt > 0) - cut_id.increase_check_sum(size_t(cut_obj_cnt)); - } -} - void ModelObject::clone_for_cut(ModelObject **obj) { (*obj) = ModelObject::new_clone(*this); - (*obj)->set_model(nullptr); + (*obj)->set_model(this->get_model()); (*obj)->sla_support_points.clear(); (*obj)->sla_drain_holes.clear(); (*obj)->sla_points_status = sla::PointsStatus::NoPoints; @@ -1814,189 +1732,11 @@ void ModelObject::clone_for_cut(ModelObject **obj) (*obj)->input_file.clear(); } -Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array& plane_points) +void ModelVolume::reset_extra_facets() { - Vec3d mid_point = {0.0, 0.0, 0.0}; - for (auto pt : plane_points) - mid_point += pt; - mid_point /= (double) plane_points.size(); - - Vec3d movement = -mid_point; - - Vec3d v01 = plane_points[1] - plane_points[0]; - Vec3d v12 = plane_points[2] - plane_points[1]; - - Vec3d plane_normal = v01.cross(v12); - plane_normal.normalize(); - - Vec3d axis = {0.0, 0.0, 0.0}; - double phi = 0.0; - Matrix3d matrix; - matrix.setIdentity(); - Geometry::rotation_from_two_vectors(plane_normal, {0.0, 0.0, 1.0}, axis, phi, &matrix); - Vec3d angles = Geometry::extract_euler_angles(matrix); - - movement = matrix * movement; - Transform3d transfo; - transfo.setIdentity(); - transfo.translate(movement); - transfo.rotate(Eigen::AngleAxisd(angles(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(angles(1), Vec3d::UnitY()) * Eigen::AngleAxisd(angles(0), Vec3d::UnitX())); - return transfo; -} - -void ModelObject::process_connector_cut( - ModelVolume *volume, - const Transform3d & instance_matrix, - const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, - ModelObject *upper, ModelObject *lower, - std::vector &dowels, - Vec3d &local_dowels_displace) -{ - assert(volume->cut_info.is_connector); - volume->cut_info.set_processed(); - - const auto volume_matrix = volume->get_matrix(); - - // ! Don't apply instance transformation for the conntectors. - // This transformation is already there - if (volume->cut_info.connector_type != CutConnectorType::Dowel) { - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - ModelVolume *vol = upper->add_volume(*volume); - vol->set_transformation(volume_matrix); - vol->apply_tolerance(); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower)) { - ModelVolume *vol = lower->add_volume(*volume); - vol->set_transformation(volume_matrix); - // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug - vol->set_type(ModelVolumeType::MODEL_PART); - } - } - else { - if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { - ModelObject *dowel{nullptr}; - // Clone the object to duplicate instances, materials etc. - clone_for_cut(&dowel); - - // add one more solid part same as connector if this connector is a dowel - ModelVolume *vol = dowel->add_volume(*volume); - vol->set_type(ModelVolumeType::MODEL_PART); - - // But discard rotation and Z-offset for this volume - vol->set_rotation(Vec3d::Zero()); - vol->set_offset(Z, 0.0); - - // Compute the displacement (in instance coordinates) to be applied to place the dowels - local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); - - dowels.push_back(dowel); - } - - // Cut the dowel - volume->apply_tolerance(); - - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); - - // add small Z offset to better preview - upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); - lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); - - // Add cut parts to the related objects - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); - add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); - } -} - -void ModelObject::process_modifier_cut( - ModelVolume *volume, - const Transform3d &instance_matrix, - const Transform3d &inverse_cut_matrix, - ModelObjectCutAttributes attributes, - ModelObject *upper, - ModelObject *lower) -{ - const auto volume_matrix = instance_matrix * volume->get_matrix(); - - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - volume->set_transformation(Geometry::Transformation(volume_matrix)); - - if (attributes.has(ModelObjectCutAttribute::CutToParts)) { - upper->add_volume(*volume); - return; - } - - // Some logic for the negative volumes/connectors. Add only needed modifiers - auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); - bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) - upper->add_volume(*volume); - if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) - lower->add_volume(*volume); -} - -void ModelObject::process_volume_cut(ModelVolume * volume, - const Transform3d & instance_matrix, - const Transform3d & cut_matrix, - ModelObjectCutAttributes attributes, - TriangleMesh & upper_mesh, - TriangleMesh & lower_mesh) -{ - const auto volume_matrix = volume->get_matrix(); - - using namespace Geometry; - - const Geometry::Transformation cut_transformation = Geometry::Transformation(cut_matrix); - const Transform3d invert_cut_matrix = cut_transformation.get_matrix(true, false, true, true).inverse() - * translation_transform(-1 * cut_transformation.get_offset()); - - // Transform the mesh by the combined transformation matrix. - // Flip the triangles in case the composite transformation is left handed. - TriangleMesh mesh(volume->mesh()); - mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); - - indexed_triangle_set upper_its, lower_its; - cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper_mesh = TriangleMesh(upper_its); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower_mesh = TriangleMesh(lower_its); -} - -void ModelObject::process_solid_part_cut(ModelVolume * volume, - const Transform3d & instance_matrix, - const Transform3d & cut_matrix, - const std::array &plane_points, - ModelObjectCutAttributes attributes, - ModelObject * upper, - ModelObject * lower, - Vec3d & local_displace) -{ - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); - - // Add required cut parts to the objects - if (attributes.has(ModelObjectCutAttribute::CutToParts)) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); - return; - } - - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - add_cut_volume(upper_mesh, upper, volume, cut_matrix); - - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { - add_cut_volume(lower_mesh, lower, volume, cut_matrix); - - // Compute the displacement (in instance coordinates) to be applied to place the upper parts - // The upper part displacement is set to half of the lower part bounding box - // this is done in hope at least a part of the upper part will always be visible and draggable - local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); - } + this->supported_facets.reset(); + this->seam_facets.reset(); + this->mmu_segmentation_facets.reset(); } static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance) @@ -2073,215 +1813,6 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan } } -// BBS: replace z with plane_points -ModelObjectPtrs ModelObject::cut(size_t instance, std::array plane_points, ModelObjectCutAttributes attributes) -{ - if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) - return {}; - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; - - // apply cut attributes for object - apply_cut_attributes(attributes); - - ModelObject* upper{ nullptr }; - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - clone_for_cut(&upper); - - ModelObject* lower{ nullptr }; - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::CutToParts)) - clone_for_cut(&lower); - - // Because transformations are going to be applied to meshes directly, - // we reset transformation of all instances and volumes, - // except for translation and Z-rotation on instances, which are preserved - // in the transformation matrix and not applied to the mesh transform. - - // const auto instance_matrix = instances[instance]->get_matrix(true); - const auto instance_matrix = Geometry::assemble_transform( - Vec3d::Zero(), // don't apply offset - instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), // BBS: do apply Z-rotation - instances[instance]->get_scaling_factor(), - instances[instance]->get_mirror() - ); - - // BBS - //z -= instances[instance]->get_offset().z(); - for (Vec3d& point : plane_points) { - point -= instances[instance]->get_offset(); - } - Transform3d inverse_cut_matrix = calculate_cut_plane_inverse_matrix(plane_points); - Transform3d cut_matrix = inverse_cut_matrix.inverse(); - - std::vector dowels; - // Displacement (in instance coordinates) to be applied to place the upper parts - Vec3d local_displace = Vec3d::Zero(); - Vec3d local_dowels_displace = Vec3d::Zero(); - - for (ModelVolume *volume : volumes) { - const auto volume_matrix = volume->get_matrix(); - - volume->supported_facets.reset(); - volume->seam_facets.reset(); - volume->mmu_segmentation_facets.reset(); - - if (! volume->is_model_part()) { - if (volume->cut_info.is_processed) { - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - //Transform3d inverse_cut_matrix = calculate_cut_plane_inverse_matrix(plane_points); - process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); - } - else { - process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels, local_dowels_displace); - } - } - else if (! volume->mesh().empty()) { - process_solid_part_cut(volume, instance_matrix, cut_matrix, plane_points, attributes, upper, lower, local_displace); - } - } - - ModelObjectPtrs res; - - if (attributes.has(ModelObjectCutAttribute::CutToParts) && !upper->volumes.empty()) { - reset_instance_transformation(upper, instance, cut_matrix); - res.push_back(upper); - } - else { - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { - reset_instance_transformation(upper, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), - attributes.has(ModelObjectCutAttribute::FlipUpper), local_displace); - - res.push_back(upper); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { - reset_instance_transformation(lower, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), - attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower)); - - res.push_back(lower); - } - - if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { - for (auto dowel : dowels) { - reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace); - - local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0)); - dowel->name += "-Dowel-" + dowel->volumes[0]->name; - res.push_back(dowel); - } - } - } - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; - - synchronize_model_after_cut(); - - return res; -} - -// BBS -ModelObjectPtrs ModelObject::segment(size_t instance, unsigned int max_extruders, double smoothing_alpha, int segment_number) -{ - BOOST_LOG_TRIVIAL(trace) << "ModelObject::segment - start"; - - // Clone the object to duplicate instances, materials etc. - ModelObject* upper = ModelObject::new_clone(*this); - - upper->set_model(nullptr); - upper->sla_support_points.clear(); - upper->sla_drain_holes.clear(); - upper->sla_points_status = sla::PointsStatus::NoPoints; - upper->clear_volumes(); - upper->input_file.clear(); - - // Because transformations are going to be applied to meshes directly, - // we reset transformation of all instances and volumes, - // except for translation and Z-rotation on instances, which are preserved - // in the transformation matrix and not applied to the mesh transform. - - // const auto instance_matrix = instances[instance]->get_matrix(true); - const auto instance_matrix = Geometry::assemble_transform( - Vec3d::Zero(), // don't apply offset - instances[instance]->get_rotation(), // BBS: keep Z-rotation - instances[instance]->get_scaling_factor(), - instances[instance]->get_mirror() - ); - - for (ModelVolume* volume : volumes) { - const auto volume_matrix = volume->get_matrix(); - - volume->supported_facets.reset(); - volume->seam_facets.reset(); - - if (!volume->is_model_part()) { - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - upper->add_volume(*volume); - } - else if (!volume->mesh().empty()) { - // Transform the mesh by the combined transformation matrix. - // Flip the triangles in case the composite transformation is left handed. - TriangleMesh mesh(volume->mesh()); - mesh.transform(instance_matrix * volume_matrix, true); - volume->reset_mesh(); - - auto mesh_segments = MeshBoolean::cgal::segment(mesh, smoothing_alpha, segment_number); - - - // Reset volume transformation except for offset - const Vec3d offset = volume->get_offset(); - volume->set_transformation(Geometry::Transformation()); - volume->set_offset(offset); - - unsigned int extruder_counter = 0; - for (int idx=0;idx 0) { - ModelVolume* vol = upper->add_volume(mesh_segment); - vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_" + std::to_string(idx); - // Don't copy the config's ID. - vol->config.assign_config(volume->config); -#if 0 - assert(vol->config.id().valid()); - assert(vol->config.id() != volume->config.id()); - vol->set_material(volume->material_id(), *volume->material()); -#else - vol->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); -#endif - } - } - } - } - - ModelObjectPtrs res; - - if (upper->volumes.size() > 0) { - upper->invalidate_bounding_box(); - - // Reset instance transformation except offset and Z-rotation - for (size_t i = 0; i < instances.size(); i++) { - auto& instance = upper->instances[i]; - const Vec3d offset = instance->get_offset(); - // BBS - //const double rot_z = instance->get_rotation()(2); - - instance->set_transformation(Geometry::Transformation()); - instance->set_offset(offset); - // BBS - //instance->set_rotation(Vec3d(0.0, 0.0, rot_z)); - } - - res.push_back(upper); - } - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::segment - end"; - - return res; -} - void ModelObject::split(ModelObjectPtrs* new_objects) { std::vector all_meshes; @@ -2758,6 +2289,14 @@ int ModelObject::get_repaired_errors_count(const int vol_idx /*= -1*/) const stats.facets_reversed + stats.backwards_edges; } +bool ModelObject::has_solid_mesh() const +{ + for (const ModelVolume* volume : volumes) + if (volume->is_model_part()) + return true; + return false; +} + void ModelVolume::set_material_id(t_model_material_id material_id) { m_material_id = material_id; @@ -2801,35 +2340,6 @@ bool ModelVolume::is_splittable() const return m_is_splittable == 1; } -void ModelVolume::apply_tolerance() -{ - assert(cut_info.is_connector); - if (!cut_info.is_processed) - return; - - Vec3d sf = get_scaling_factor(); - // make a "hole" wider - double size_scale = 1.f; - if (abs(cut_info.radius - 0) < EPSILON) // For compatibility with old files - size_scale = 1.f + double(cut_info.radius_tolerance); - else - size_scale = (double(cut_info.radius) + double(cut_info.radius_tolerance)) / double(cut_info.radius); - - sf[X] *= size_scale; - sf[Y] *= size_scale; - - // make a "hole" dipper - double height_scale = 1.f; - if (abs(cut_info.height - 0) < EPSILON) // For compatibility with old files - height_scale = 1.f + double(cut_info.height_tolerance); - else - height_scale = (double(cut_info.height) + double(cut_info.height_tolerance)) / double(cut_info.height); - - sf[Z] *= height_scale; - - set_scaling_factor(sf); -} - // BBS std::vector ModelVolume::get_extruders() const { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 481552c058..02220c2aad 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -246,79 +246,92 @@ private: }; enum class CutConnectorType : int { - Plug, - Dowel, - Undef + Plug + , Dowel + , Snap + , Undef }; enum class CutConnectorStyle : int { - Prizm, - Frustum, - Undef + Prism + , Frustum + , Undef //,Claw }; enum class CutConnectorShape : int { - Triangle, - Square, - Hexagon, - Circle, - Undef + Triangle + , Square + , Hexagon + , Circle + , Undef //,D-shape }; struct CutConnectorAttributes { - CutConnectorType type{CutConnectorType::Plug}; - CutConnectorStyle style{CutConnectorStyle::Prizm}; - CutConnectorShape shape{CutConnectorShape::Circle}; + CutConnectorType type{ CutConnectorType::Plug }; + CutConnectorStyle style{ CutConnectorStyle::Prism }; + CutConnectorShape shape{ CutConnectorShape::Circle }; CutConnectorAttributes() {} - CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh) : type(t), style(st), shape(sh) {} + CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh) + : type(t), style(st), shape(sh) + {} - CutConnectorAttributes(const CutConnectorAttributes &rhs) : CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {} + CutConnectorAttributes(const CutConnectorAttributes& rhs) : + CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {} - bool operator==(const CutConnectorAttributes &other) const; + bool operator==(const CutConnectorAttributes& other) const; - bool operator!=(const CutConnectorAttributes &other) const { return !(other == (*this)); } + bool operator!=(const CutConnectorAttributes& other) const { return !(other == (*this)); } - bool operator<(const CutConnectorAttributes &other) const - { - return this->type < other.type || (this->type == other.type && this->style < other.style) || - (this->type == other.type && this->style == other.style && this->shape < other.shape); + bool operator<(const CutConnectorAttributes& other) const { + return this->type < other.type || + (this->type == other.type && this->style < other.style) || + (this->type == other.type && this->style == other.style && this->shape < other.shape); } - template inline void serialize(Archive &ar) { ar(type, style, shape); } + template inline void serialize(Archive& ar) { + ar(type, style, shape); + } }; struct CutConnector { - Vec3d pos; - Transform3d rotation_m; - float radius; - float height; - float radius_tolerance; // [0.f : 1.f] - float height_tolerance; // [0.f : 1.f] + Vec3d pos; + Transform3d rotation_m; + float radius; + float height; + float radius_tolerance;// [0.f : 1.f] + float height_tolerance;// [0.f : 1.f] + float z_angle {0.f}; CutConnectorAttributes attribs; - CutConnector() : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {} - - CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes) - : pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes) + CutConnector() + : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f), z_angle(0.f) {} - CutConnector(const CutConnector &rhs) : CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {} + CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, float za, CutConnectorAttributes attributes) + : pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), z_angle(za), attribs(attributes) + {} - bool operator==(const CutConnector &other) const; + CutConnector(const CutConnector& rhs) : + CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.z_angle, rhs.attribs) {} - bool operator!=(const CutConnector &other) const { return !(other == (*this)); } + bool operator==(const CutConnector& other) const; - template inline void serialize(Archive &ar) { ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs); } + bool operator!=(const CutConnector& other) const { return !(other == (*this)); } + + template inline void serialize(Archive& ar) { + ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, z_angle, attribs); + } }; using CutConnectors = std::vector; + // Declared outside of ModelVolume, so it could be forward declared. enum class ModelVolumeType : int { INVALID = -1, @@ -326,13 +339,9 @@ enum class ModelVolumeType : int { NEGATIVE_VOLUME, PARAMETER_MODIFIER, SUPPORT_BLOCKER, - SUPPORT_ENFORCER + SUPPORT_ENFORCER, }; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, CutToParts, InvalidateCutInfo }; -using ModelObjectCutAttributes = enum_bitmask; -ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); - // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, @@ -371,6 +380,10 @@ public: // Holes to be drilled into the object so resin can flow out sla::DrainHoles sla_drain_holes; + // Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform + CutConnectors cut_connectors; + CutObjectBase cut_id; + /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment @@ -380,10 +393,6 @@ public: // BBS: save for compare with new load volumes std::vector volume_ids; - // Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform - CutConnectors cut_connectors; - CutObjectBase cut_id; - Model* get_model() { return m_model; } const Model* get_model() const { return m_model; } // BBS: production extension @@ -480,52 +489,13 @@ public: size_t materials_count() const; size_t facets_count() const; size_t parts_count() const; - - bool is_cut() const { return cut_id.id().valid(); } - bool has_connectors() const; - static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); - void apply_cut_connectors(const std::string &name); // invalidate cut state for this object and its connectors/volumes void invalidate_cut(); // delete volumes which are marked as connector for this object void delete_connectors(); - void synchronize_model_after_cut(); - void apply_cut_attributes(ModelObjectCutAttributes attributes); void clone_for_cut(ModelObject **obj); - Transform3d calculate_cut_plane_inverse_matrix(const std::array &plane_points); - void process_connector_cut(ModelVolume *volume, - const Transform3d & instance_matrix, - const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, - ModelObject *upper, ModelObject *lower, - std::vector &dowels, - Vec3d &local_dowels_displace); - void process_modifier_cut(ModelVolume * volume, - const Transform3d & instance_matrix, - const Transform3d & inverse_cut_matrix, - ModelObjectCutAttributes attributes, - ModelObject * upper, - ModelObject * lower); - void process_volume_cut(ModelVolume * volume, - const Transform3d & instance_matrix, - const Transform3d & cut_matrix, - ModelObjectCutAttributes attributes, - TriangleMesh & upper_mesh, - TriangleMesh & lower_mesh); - void process_solid_part_cut(ModelVolume * volume, - const Transform3d & instance_matrix, - const Transform3d & cut_matrix, - const std::array &plane_points, - ModelObjectCutAttributes attributes, - ModelObject * upper, - ModelObject * lower, - Vec3d & local_displace); - // BBS: replace z with plane_points - ModelObjectPtrs cut(size_t instance, std::array plane_points, ModelObjectCutAttributes attributes); - // BBS - ModelObjectPtrs segment(size_t instance, unsigned int max_extruders, double smoothing_alpha = 0.5, int segment_number = 5); - void split(ModelObjectPtrs* new_objects); + void split(ModelObjectPtrs*new_objects); void merge(); // BBS: Boolean opts - Musang King @@ -553,6 +523,10 @@ public: // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) int get_repaired_errors_count(const int vol_idx = -1) const; + // Detect if object has at least one solid mash + bool has_solid_mesh() const; + bool is_cut() const { return cut_id.id().valid(); } + bool has_connectors() const; private: friend class Model; // This constructor assigns new ID to this ModelObject and its config. @@ -853,37 +827,45 @@ public: }; Source source; - // struct used by cut command + // struct used by cut command // It contains information about connetors struct CutInfo { - bool is_connector{false}; - bool is_processed{true}; - CutConnectorType connector_type{CutConnectorType::Plug}; - float radius{0.f}; - float height{0.f}; - float radius_tolerance{0.f}; // [0.f : 1.f] - float height_tolerance{0.f}; // [0.f : 1.f] + bool is_from_upper{ true }; + bool is_connector{ false }; + bool is_processed{ true }; + CutConnectorType connector_type{ CutConnectorType::Plug }; + float radius_tolerance{ 0.f };// [0.f : 1.f] + float height_tolerance{ 0.f };// [0.f : 1.f] CutInfo() = default; - CutInfo(CutConnectorType type, float radius_, float height_, float rad_tolerance, float h_tolerance, bool processed = false) - : is_connector(true), is_processed(processed), connector_type(type) - , radius(radius_), height(height_), radius_tolerance(rad_tolerance), height_tolerance(h_tolerance) + CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false) : + is_connector(true), + is_processed(processed), + connector_type(type), + radius_tolerance(rad_tolerance), + height_tolerance(h_tolerance) {} void set_processed() { is_processed = true; } - void invalidate() { is_connector = false; } + void invalidate() { is_connector = false; } + void reset_from_upper() { is_from_upper = true; } - template inline void serialize(Archive &ar) { ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); } + template inline void serialize(Archive& ar) { + ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); + } }; - CutInfo cut_info; + CutInfo cut_info; - bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } - void invalidate_cut_info() { cut_info.invalidate(); } + bool is_from_upper() const { return cut_info.is_from_upper; } + void reset_from_upper() { cut_info.reset_from_upper(); } + + bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } + void invalidate_cut_info() { cut_info.invalidate(); } // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } - const TriangleMesh* mesh_ptr() const { return m_mesh.get(); } + std::shared_ptr mesh_ptr() const { return m_mesh; } void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared(mesh); } @@ -922,6 +904,7 @@ public: bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } t_model_material_id material_id() const { return m_material_id; } + void reset_extra_facets(); void set_material_id(t_model_material_id material_id); ModelMaterial* material() const; void set_material(t_model_material_id material_id, const ModelMaterial &material); @@ -931,8 +914,6 @@ public: bool is_splittable() const; - void apply_tolerance(); - // BBS std::vector get_extruders() const; void update_extruder_count(size_t extruder_count); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 1ad0c92f8a..e25068572d 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1404,7 +1404,7 @@ void PerimeterGenerator::apply_extra_perimeters(ExPolygons &infill_area) } // Reorient loop direction -static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_overhang_contour, bool steep_overhang_hole) +static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_overhang_contour, bool steep_overhang_hole, bool reverse_internal_only) { if (steep_overhang_hole || steep_overhang_contour) { for (auto entity : entities) { @@ -1412,7 +1412,18 @@ static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_ ExtrusionLoop *eloop = static_cast(entity); // Only reverse when needed bool need_reverse = ((eloop->loop_role() & elrHole) == elrHole) ? steep_overhang_hole : steep_overhang_contour; - if (need_reverse) { + + bool isExternal = false; + if(reverse_internal_only){ + for(auto path : eloop->paths){ + if(path.role() == erExternalPerimeter){ + isExternal = true; + break; + } + } + } + + if (need_reverse && !isExternal) { eloop->make_clockwise(); } } @@ -1710,7 +1721,7 @@ void PerimeterGenerator::process_classic() bool steep_overhang_contour = false; bool steep_overhang_hole = false; ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls, steep_overhang_contour, steep_overhang_hole); - reorient_perimeters(entities, steep_overhang_contour, steep_overhang_hole); + reorient_perimeters(entities, steep_overhang_contour, steep_overhang_hole, this->config->overhang_reverse_internal_only); // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim @@ -2232,7 +2243,7 @@ void PerimeterGenerator::process_arachne() bool steep_overhang_contour = false; bool steep_overhang_hole = false; if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions, steep_overhang_contour, steep_overhang_hole); !extrusion_coll.empty()) { - reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole); + reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole, this->config->overhang_reverse_internal_only); this->loops->append(extrusion_coll); } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 6c6bac8800..73252f4459 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -55,7 +55,7 @@ using Vec2f = Eigen::Matrix; using Vec3f = Eigen::Matrix; using Vec2d = Eigen::Matrix; using Vec3d = Eigen::Matrix; -// BBS +using Vec4f = Eigen::Matrix; using Vec4d = Eigen::Matrix; using Points = std::vector; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index c8c158f930..c9777b3c38 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -726,7 +726,7 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle) static std::vector s_Preset_print_options { "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "slicing_mode", "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", - "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold", + "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "seam_position", "staggered_inner_seams", "wall_infill_order", "sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern", "infill_direction", "minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 723398f751..cefc09347c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -840,7 +840,15 @@ void PrintConfigDef::init_fff_params() def->label = L("Reverse on odd"); def->full_label = L("Overhang reversal"); def->category = L("Quality"); - def->tooltip = L("Extrude perimeters that have a part over an overhang in the reverse direction on odd layers. This alternating pattern can drastically improve steep overhang."); + def->tooltip = L("Extrude perimeters that have a part over an overhang in the reverse direction on odd layers. This alternating pattern can drastically improve steep overhangs.\n\nThis setting can also help reduce part warping due to the reduction of stresses in the part walls."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("overhang_reverse_internal_only", coBool); + def->label = L("Reverse only internal perimeters"); + def->full_label = L("Reverse only internal perimeters"); + def->category = L("Quality"); + def->tooltip = L("Apply the reverse perimeters logic only on internal perimeters. \n\nThis setting greatly reduces part stresses as they are now distributed in alternating directions. This should reduce part warping while also maintaining external wall quality. This feature can be very useful for warp prone material, like ABS/ASA, and also for elastic filaments, like TPU and Silk PLA. It can also help reduce warping on floating regions over supports.\n\nFor this setting to be the most effective, it is recomended to set the Reverse Threshold to 0 so that all internal walls print in alternating directions on odd layers irrespective of their overhang degree."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 6404b367ae..40cc7a6d62 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -877,6 +877,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, hole_to_polyhole_threshold)) ((ConfigOptionBool, hole_to_polyhole_twisted)) ((ConfigOptionBool, overhang_reverse)) + ((ConfigOptionBool, overhang_reverse_internal_only)) ((ConfigOptionFloatOrPercent, overhang_reverse_threshold)) ) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8d6175cc0a..48da72f5f3 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1097,6 +1097,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "fuzzy_skin_point_distance" || opt_key == "detect_overhang_wall" || opt_key == "overhang_reverse" + || opt_key == "overhang_reverse_internal_only" || opt_key == "overhang_reverse_threshold" //BBS || opt_key == "enable_overhang_speed" diff --git a/src/libslic3r/Semver.hpp b/src/libslic3r/Semver.hpp index cdc96f108b..4d64b1c7db 100644 --- a/src/libslic3r/Semver.hpp +++ b/src/libslic3r/Semver.hpp @@ -110,10 +110,30 @@ public: void set_maj(int maj) { ver.major = maj; } void set_min(int min) { ver.minor = min; } void set_patch(int patch) { ver.patch = patch; } - void set_metadata(boost::optional meta) { ver.metadata = meta ? strdup(*meta) : nullptr; } - void set_metadata(const char *meta) { ver.metadata = meta ? strdup(meta) : nullptr; } - void set_prerelease(boost::optional pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; } - void set_prerelease(const char *pre) { ver.prerelease = pre ? strdup(pre) : nullptr; } + void set_metadata(boost::optional meta) + { + if (ver.metadata) + free(ver.metadata); + ver.metadata = meta ? strdup(*meta) : nullptr; + } + void set_metadata(const char *meta) + { + if (ver.metadata) + free(ver.metadata); + ver.metadata = meta ? strdup(meta) : nullptr; + } + void set_prerelease(boost::optional pre) + { + if (ver.prerelease) + free(ver.prerelease); + ver.prerelease = pre ? strdup(*pre) : nullptr; + } + void set_prerelease(const char *pre) + { + if (ver.prerelease) + free(ver.prerelease); + ver.prerelease = pre ? strdup(pre) : nullptr; + } // Comparison bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; } diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp new file mode 100644 index 0000000000..976387c21f --- /dev/null +++ b/src/libslic3r/SurfaceMesh.hpp @@ -0,0 +1,167 @@ +///|/ Copyright (c) Prusa Research 2022 Lukáš Matěna @lukasmatena +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_SurfaceMesh_hpp_ +#define slic3r_SurfaceMesh_hpp_ + +#include +#include + +#include "boost/container/small_vector.hpp" + +namespace Slic3r { + +class TriangleMesh; + + + +enum Face_index : int; + +class Halfedge_index { + friend class SurfaceMesh; + +public: + Halfedge_index() : m_face(Face_index(-1)), m_side(0) {} + Face_index face() const { return m_face; } + unsigned char side() const { return m_side; } + bool is_invalid() const { return int(m_face) < 0; } + bool operator!=(const Halfedge_index& rhs) const { return ! ((*this) == rhs); } + bool operator==(const Halfedge_index& rhs) const { return m_face == rhs.m_face && m_side == rhs.m_side; } + +private: + Halfedge_index(int face_idx, unsigned char side_idx) : m_face(Face_index(face_idx)), m_side(side_idx) {} + + Face_index m_face; + unsigned char m_side; +}; + + + +class Vertex_index { + friend class SurfaceMesh; + +public: + Vertex_index() : m_face(Face_index(-1)), m_vertex_idx(0) {} + bool is_invalid() const { return int(m_face) < 0; } + bool operator==(const Vertex_index& rhs) const = delete; // Use SurfaceMesh::is_same_vertex. + +private: + Vertex_index(int face_idx, unsigned char vertex_idx) : m_face(Face_index(face_idx)), m_vertex_idx(vertex_idx) {} + + Face_index m_face; + unsigned char m_vertex_idx; +}; + + + +class SurfaceMesh { +public: + explicit SurfaceMesh(const indexed_triangle_set& its) + : m_its(its), + m_face_neighbors(its_face_neighbors_par(its)) + {} + SurfaceMesh(const SurfaceMesh&) = delete; + SurfaceMesh& operator=(const SurfaceMesh&) = delete; + + Vertex_index source(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side); } + Vertex_index target(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side == 2 ? 0 : h.m_side + 1); } + Face_index face(Halfedge_index h) const { assert(! h.is_invalid()); return h.m_face; } + + Halfedge_index next(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side + 1) % 3; return h; } + Halfedge_index prev(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side == 0 ? 2 : h.m_side - 1); return h; } + Halfedge_index halfedge(Vertex_index v) const { return Halfedge_index(v.m_face, (v.m_vertex_idx == 0 ? 2 : v.m_vertex_idx - 1)); } + Halfedge_index halfedge(Face_index f) const { return Halfedge_index(f, 0); } + Halfedge_index opposite(Halfedge_index h) const { + if (h.is_invalid()) + return h; + + int face_idx = m_face_neighbors[h.m_face][h.m_side]; + Halfedge_index h_candidate = halfedge(Face_index(face_idx)); + + if (h_candidate.is_invalid()) + return Halfedge_index(); // invalid + + for (int i=0; i<3; ++i) { + if (is_same_vertex(source(h_candidate), target(h))) { + // Meshes in PrusaSlicer should be fixed enough for the following not to happen. + assert(is_same_vertex(target(h_candidate), source(h))); + return h_candidate; + } + h_candidate = next(h_candidate); + } + return Halfedge_index(); // invalid + } + + Halfedge_index next_around_target(Halfedge_index h) const { return opposite(next(h)); } + Halfedge_index prev_around_target(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : prev(op)); } + Halfedge_index next_around_source(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : next(op)); } + Halfedge_index prev_around_source(Halfedge_index h) const { return opposite(prev(h)); } + Halfedge_index halfedge(Vertex_index source, Vertex_index target) const + { + Halfedge_index hi(source.m_face, source.m_vertex_idx); + assert(! hi.is_invalid()); + + const Vertex_index orig_target = this->target(hi); + Vertex_index current_target = orig_target; + + while (! is_same_vertex(current_target, target)) { + hi = next_around_source(hi); + if (hi.is_invalid()) + break; + current_target = this->target(hi); + if (is_same_vertex(current_target, orig_target)) + return Halfedge_index(); // invalid + } + + return hi; + } + + const stl_vertex& point(Vertex_index v) const { return m_its.vertices[m_its.indices[v.m_face][v.m_vertex_idx]]; } + + size_t degree(Vertex_index v) const + { + // In case the mesh is broken badly, the loop might end up to be infinite, + // never getting back to the first halfedge. Remember list of all half-edges + // and trip if any is encountered for the second time. + Halfedge_index h_first = halfedge(v); + boost::container::small_vector he_visited; + Halfedge_index h = next_around_target(h_first); + size_t degree = 2; + while (! h.is_invalid() && h != h_first) { + he_visited.emplace_back(h); + h = next_around_target(h); + if (std::find(he_visited.begin(), he_visited.end(), h) == he_visited.end()) + return 0; + ++degree; + } + return h.is_invalid() ? 0 : degree - 1; + } + + size_t degree(Face_index f) const { + size_t total = 0; + for (unsigned char i=0; i<3; ++i) { + size_t d = degree(Vertex_index(f, i)); + if (d == 0) + return 0; + total += d; + } + assert(total - 6 >= 0); + return total - 6; // we counted 3 halfedges from f, and one more for each neighbor + } + + bool is_border(Halfedge_index h) const { return m_face_neighbors[h.m_face][h.m_side] == -1; } + + bool is_same_vertex(const Vertex_index& a, const Vertex_index& b) const { return m_its.indices[a.m_face][a.m_vertex_idx] == m_its.indices[b.m_face][b.m_vertex_idx]; } + Vec3i get_face_neighbors(Face_index face_id) const { assert(int(face_id) < int(m_face_neighbors.size())); return m_face_neighbors[face_id]; } + + + +private: + const std::vector m_face_neighbors; + const indexed_triangle_set& m_its; +}; + +} //namespace Slic3r + +#endif // slic3r_SurfaceMesh_hpp_ diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index aa014a6dc9..d8dc5a7d42 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -12,8 +12,6 @@ #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with camera related data #define ENABLE_CAMERA_STATISTICS 0 -// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) -#define ENABLE_RENDER_PICKING_PASS 0 // Enable extracting thumbnails from selected gcode and save them as png files #define ENABLE_THUMBNAIL_GENERATOR_DEBUG 0 // Disable synchronization of unselected instances @@ -61,6 +59,8 @@ #define ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT (1 && ENABLE_2_4_0_BETA2) // Enable fit print volume command for circular printbeds #define ENABLE_ENHANCED_PRINT_VOLUME_FIT (1 && ENABLE_2_4_0_BETA2) +// Enable picking using raytracing +#define ENABLE_RAYCAST_PICKING_DEBUG 0 #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b5d0d10883..92557dfdb5 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,3 +1,18 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2019 Jason Tibbitts @jasontibbitts +///|/ Copyright (c) 2019 Sijmen Schoon +///|/ Copyright (c) 2016 Joseph Lenox @lordofhyphens +///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard +///|/ Copyright (c) 2014 Miro Hrončok @hroncok +///|/ Copyright (c) 2014 Petr Ledvina @ledvinap +///|/ +///|/ ported from lib/Slic3r/TriangleMesh.pm: +///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2012 - 2013 Mark Hindess +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "Exception.hpp" #include "TriangleMesh.hpp" #include "TriangleMeshSlicer.hpp" @@ -958,6 +973,51 @@ indexed_triangle_set its_make_cylinder(double r, double h, double fa) return mesh; } +indexed_triangle_set its_make_frustum(double r, double h, double fa) +{ + indexed_triangle_set mesh; + size_t n_steps = (size_t)ceil(2. * PI / fa); + double angle_step = 2. * PI / n_steps; + + auto &vertices = mesh.vertices; + auto &facets = mesh.indices; + vertices.reserve(2 * n_steps + 2); + facets.reserve(4 * n_steps); + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.emplace_back(Vec3f(0.f, 0.f, 0.f)); + vertices.emplace_back(Vec3f(0.f, 0.f, float(h))); + + // for each line along the polygon approximating the top/bottom of the + // circle, generate four points and four facets (2 for the wall, 2 for the + // top and bottom. + // Special case: Last line shares 2 vertices with the first line. + Vec2f vec_top = Eigen::Rotation2Df(0.f) * Eigen::Vector2f(0, 0.5f*r); + Vec2f vec_botton = Eigen::Rotation2Df(0.f) * Eigen::Vector2f(0, r); + + vertices.emplace_back(Vec3f(vec_botton(0), vec_botton(1), 0.f)); + vertices.emplace_back(Vec3f(vec_top(0), vec_top(1), float(h))); + for (size_t i = 1; i < n_steps; ++i) { + vec_top = Eigen::Rotation2Df(angle_step * i) * Eigen::Vector2f(0, 0.5f*float(r)); + vec_botton = Eigen::Rotation2Df(angle_step * i) * Eigen::Vector2f(0, float(r)); + vertices.emplace_back(Vec3f(vec_botton(0), vec_botton(1), 0.f)); + vertices.emplace_back(Vec3f(vec_top(0), vec_top(1), float(h))); + int id = (int)vertices.size() - 1; + facets.emplace_back( 0, id - 1, id - 3); // top + facets.emplace_back(id, 1, id - 2); // bottom + facets.emplace_back(id, id - 2, id - 3); // upper-right of side + facets.emplace_back(id, id - 3, id - 1); // bottom-left of side + } + // Connect the last set of vertices with the first. + int id = (int)vertices.size() - 1; + facets.emplace_back( 0, 2, id - 1); + facets.emplace_back( 3, 1, id); + facets.emplace_back(id, 2, 3); + facets.emplace_back(id, id - 1, 2); + + return mesh; +} + indexed_triangle_set its_make_cone(double r, double h, double fa) { indexed_triangle_set mesh; @@ -984,61 +1044,6 @@ indexed_triangle_set its_make_cone(double r, double h, double fa) return mesh; } -// Generates mesh for a frustum dowel centered about the origin, using the count of sectors -// Note: This function uses code for sphere generation, but for stackCount = 2; -indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount) -{ - int stackCount = 2; - float sectorStep = float(2. * M_PI / sectorCount); - float stackStep = float(M_PI / stackCount); - - indexed_triangle_set mesh; - auto& vertices = mesh.vertices; - vertices.reserve((stackCount - 1) * sectorCount + 2); - for (int i = 0; i <= stackCount; ++i) { - // from pi/2 to -pi/2 - double stackAngle = 0.5 * M_PI - stackStep * i; - double xy = radius * cos(stackAngle); - double z = radius * sin(stackAngle); - if (i == 0 || i == stackCount) - vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle)))); - else - for (int j = 0; j < sectorCount; ++j) { - // from 0 to 2pi - double sectorAngle = sectorStep * j + 0.25 * M_PI; - vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); - } - } - - auto& facets = mesh.indices; - facets.reserve(2 * (stackCount - 1) * sectorCount); - for (int i = 0; i < stackCount; ++i) { - // Beginning of current stack. - int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); - int k1_first = k1; - // Beginning of next stack. - int k2 = (i == 0) ? 1 : (k1 + sectorCount); - int k2_first = k2; - for (int j = 0; j < sectorCount; ++j) { - // 2 triangles per sector excluding first and last stacks - int k1_next = k1; - int k2_next = k2; - if (i != 0) { - k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); - facets.emplace_back(k1, k2, k1_next); - } - if (i + 1 != stackCount) { - k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); - facets.emplace_back(k1_next, k2, k2_next); - } - k1 = k1_next; - k2 = k2_next; - } - } - - return mesh; -} - indexed_triangle_set its_make_pyramid(float base, float height) { float a = base / 2.f; @@ -1116,6 +1121,182 @@ indexed_triangle_set its_make_sphere(double radius, double fa) return mesh; } +// Generates mesh for a frustum dowel centered about the origin, using the count of sectors +// Note: This function uses code for sphere generation, but for stackCount = 2; +indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount) +{ + int stackCount = 2; + float sectorStep = float(2. * M_PI / sectorCount); + float stackStep = float(M_PI / stackCount); + + indexed_triangle_set mesh; + auto& vertices = mesh.vertices; + vertices.reserve((stackCount - 1) * sectorCount + 2); + for (int i = 0; i <= stackCount; ++i) { + // from pi/2 to -pi/2 + double stackAngle = 0.5 * M_PI - stackStep * i; + double xy = radius * cos(stackAngle); + double z = radius * sin(stackAngle); + if (i == 0 || i == stackCount) + vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle)))); + else + for (int j = 0; j < sectorCount; ++j) { + // from 0 to 2pi + double sectorAngle = sectorStep * j + 0.25 * M_PI; + vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); + } + } + + auto& facets = mesh.indices; + facets.reserve(2 * (stackCount - 1) * sectorCount); + for (int i = 0; i < stackCount; ++i) { + // Beginning of current stack. + int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + int k1_first = k1; + // Beginning of next stack. + int k2 = (i == 0) ? 1 : (k1 + sectorCount); + int k2_first = k2; + for (int j = 0; j < sectorCount; ++j) { + // 2 triangles per sector excluding first and last stacks + int k1_next = k1; + int k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + facets.emplace_back(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + facets.emplace_back(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + + return mesh; +} + +indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion) +{ + const float radius = (float)r; + const float height = (float)h; + const size_t sectors_cnt = 10; //(float)fa; + const float halfPI = 0.5f * (float)PI; + + const float space_len = space_proportion * radius; + + const float b_len = radius; + const float m_len = (1 + bulge_proportion) * radius; + const float t_len = 0.5f * radius; + + const float b_height = 0.f; + const float m_height = 0.5f * height; + const float t_height = height; + + const float b_angle = acos(space_len/b_len); + const float t_angle = acos(space_len/t_len); + + const float b_angle_step = b_angle / (float)sectors_cnt; + const float t_angle_step = t_angle / (float)sectors_cnt; + + const Vec2f b_vec = Eigen::Vector2f(0, b_len); + const Vec2f t_vec = Eigen::Vector2f(0, t_len); + + + auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector& vertices, float b_angle, float t_angle, const Vec2f& m_vec) { + Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec; + Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec; + Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec; + + vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); + vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); + vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); + }; + + auto add_side_facets = [](std::vector& facets, int vertices_cnt, int frst_id, int scnd_id) { + int id = vertices_cnt - 1; + + facets.emplace_back(frst_id, id - 2, id - 5); + + facets.emplace_back(id - 2, id - 1, id - 5); + facets.emplace_back(id - 1, id - 4, id - 5); + facets.emplace_back(id - 4, id - 1, id); + facets.emplace_back(id, id - 3, id - 4); + + facets.emplace_back(id, scnd_id, id - 3); + }; + + const float f = (b_len - m_len) / m_len; // Flattening + + auto get_m_len = [b_len, f](float angle) { + const float rad_sqr = b_len * b_len; + const float sin_sqr = sin(angle) * sin(angle); + const float f_sqr = (1-f)*(1-f); + return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr)); + }; + + auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len, + b_height, t_height, b_angle, t_angle, b_angle_step, t_angle_step] + (indexed_triangle_set& mesh, float center_x, float angle_rotation, int frst_vertex_id) { + auto& vertices = mesh.vertices; + auto& facets = mesh.indices; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.emplace_back(Vec3f(center_x, 0.f, b_height)); + vertices.emplace_back(Vec3f(center_x, 0.f, t_height)); + + float b_angle_start = angle_rotation - b_angle; + float t_angle_start = angle_rotation - t_angle; + const float b_angle_stop = angle_rotation + b_angle; + + const int frst_id = frst_vertex_id; + const int scnd_id = frst_id + 1; + + // add first side vertices and internal facets + { + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, id - 2, id - 1); + facets.emplace_back(frst_id, id - 1, id); + facets.emplace_back(frst_id, id, scnd_id); + } + + // add d side vertices and facets + while (!is_approx(b_angle_start, b_angle_stop)) { + b_angle_start += b_angle_step; + t_angle_start += t_angle_step; + + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + add_side_facets(facets, (int)vertices.size(), frst_id, scnd_id); + } + + // add last internal facets to close the mesh + { + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, scnd_id, id); + facets.emplace_back(frst_id, id, id - 1); + facets.emplace_back(frst_id, id - 1, id - 2); + } + }; + + + indexed_triangle_set mesh; + + mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2)); + mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6)); + + add_sub_mesh(mesh, -space_len, halfPI , 0); + add_sub_mesh(mesh, space_len, 3 * halfPI, (int)mesh.vertices.size()); + + return mesh; +} + indexed_triangle_set its_convex_hull(const std::vector &pts) { std::vector dst_vertices; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 9afaddd4eb..12002cc535 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -1,3 +1,14 @@ +///|/ Copyright (c) Prusa Research 2017 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Enrico Turri @enricoturri1966, Filip Sykala @Jony01 +///|/ Copyright (c) 2019 Sijmen Schoon +///|/ Copyright (c) 2016 Joseph Lenox @lordofhyphens +///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel +///|/ +///|/ ported from lib/Slic3r/TriangleMesh.pm: +///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2012 - 2013 Mark Hindess +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_TriangleMesh_hpp_ #define slic3r_TriangleMesh_hpp_ @@ -337,9 +348,11 @@ indexed_triangle_set its_make_cube(double x, double y, double z); indexed_triangle_set its_make_prism(float width, float length, float height); indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360)); +indexed_triangle_set its_make_frustum(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount); indexed_triangle_set its_make_pyramid(float base, float height); indexed_triangle_set its_make_sphere(double radius, double fa); +indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f); indexed_triangle_set its_convex_hull(const std::vector &pts); inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); } diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 3fd0da6604..204b267c22 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2021 - 2023 Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Pavel Mikuš @Godrak, Lukáš Hejl @hejllukas +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "Tesselate.hpp" @@ -2300,79 +2304,4 @@ void cut_mesh(const indexed_triangle_set& mesh, float z, indexed_triangle_set* u } } -// BBS: implement plane cut with cgal -static Vec3d calc_plane_normal(const std::array& plane_points) -{ - Vec3d v01 = plane_points[1] - plane_points[0]; - Vec3d v12 = plane_points[2] - plane_points[1]; - - Vec3d plane_normal = v01.cross(v12); - plane_normal.normalize(); - return plane_normal; -} - -void cut_mesh -( - const indexed_triangle_set& mesh, // model object coordinate - std::array plane_points, // model object coordinate - indexed_triangle_set* upper, - indexed_triangle_set* lower, - bool triangulate_caps -) -{ - assert(upper || lower); - if (upper == nullptr && lower == nullptr) - return; - - BOOST_LOG_TRIVIAL(trace) << "cut_mesh - slicing object"; - - Vec3d plane_normal = calc_plane_normal(plane_points); - if (std::abs(plane_normal(0)) < EPSILON && std::abs(plane_normal(1)) < EPSILON) { - cut_mesh(mesh, plane_points[0](2), upper, lower); - return; - } - - // BBS - if (std::abs(plane_normal(2)) < EPSILON) { - // keep the side on the normal direction - } - else if (plane_normal(2) < 0.0) { - std::reverse(plane_points.begin(), plane_points.end()); - } - plane_normal = calc_plane_normal(plane_points); - - Vec3d mid_point = { 0.0, 0.0, 0.0 }; - for (auto pt : plane_points) - mid_point += pt; - mid_point /= (double)plane_points.size(); - Vec3d movement = -mid_point; - - Vec3d axis = { 0.0, 0.0, 0.0 }; - double phi = 0.0; - Matrix3d matrix; - matrix.setIdentity(); - Geometry::rotation_from_two_vectors(plane_normal, { 0.0, 0.0, 1.0 }, axis, phi, &matrix); - Vec3d angles = Geometry::extract_euler_angles(matrix); - - movement = matrix * movement; - Transform3d transfo; - transfo.setIdentity(); - transfo.translate(movement); - transfo.rotate(Eigen::AngleAxisd(angles(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(angles(1), Vec3d::UnitY()) * Eigen::AngleAxisd(angles(0), Vec3d::UnitX())); - - indexed_triangle_set mesh_temp = mesh; - its_transform(mesh_temp, transfo); - cut_mesh(mesh_temp, 0., upper, lower); - - Transform3d transfo_inv = transfo.inverse(); - if (upper) { - its_transform(*upper, transfo_inv); - } - - if (lower) { - its_transform(*lower, transfo_inv); - } -} - - } // namespace Slic3r diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index 1a8b643629..a7ad62acd9 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2021 - 2022 Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_TriangleMeshSlicer_hpp_ #define slic3r_TriangleMeshSlicer_hpp_ @@ -130,14 +134,6 @@ void cut_mesh( indexed_triangle_set *lower, bool triangulate_caps = true); -// BBS -void cut_mesh( - const indexed_triangle_set &mesh, - std::array plane_points, - indexed_triangle_set *upper, - indexed_triangle_set *lower, - bool triangulate_caps = true); - -} +} // namespace Slic3r #endif // slic3r_TriangleMeshSlicer_hpp_ diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 08f10b2887..ce12a33486 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -160,6 +160,11 @@ void flush_logs(); // This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded. typedef std::string local_encoded_string; +// Returns next utf8 sequence length. =number of bytes in string, that creates together one utf-8 character. +// Starting at pos. ASCII characters returns 1. Works also if pos is in the middle of the sequence. +extern size_t get_utf8_sequence_length(const std::string& text, size_t pos = 0); +extern size_t get_utf8_sequence_length(const char *seq, size_t size); + // Convert an UTF-8 encoded string into local coding. // On Windows, the UTF-8 string is converted to a local 8-bit code page. // On OSX and Linux, this function does no conversion and returns a copy of the source string. diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index f5915175c7..e31656cda9 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1,3 +1,9 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Pavel Mikuš @Godrak, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, David Kocík @kocikdav, Roman Beránek @zavorka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2021 Justin Schuh @jschuh +///|/ Copyright (c) Slic3r 2013 - 2015 Alessandro Ranellucci @alranel +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "Utils.hpp" #include "I18N.hpp" @@ -1030,6 +1036,76 @@ bool is_shapes_dir(const std::string& dir) namespace Slic3r { +size_t get_utf8_sequence_length(const std::string& text, size_t pos) +{ + assert(pos < text.size()); + return get_utf8_sequence_length(text.c_str() + pos, text.size() - pos); +} + +size_t get_utf8_sequence_length(const char *seq, size_t size) +{ + size_t length = 0; + unsigned char c = seq[0]; + if (c < 0x80) { // 0x00-0x7F + // is ASCII letter + length++; + } + // Bytes 0x80 to 0xBD are trailer bytes in a multibyte sequence. + // pos is in the middle of a utf-8 sequence. Add the utf-8 trailer bytes. + else if (c < 0xC0) { // 0x80-0xBF + length++; + while (length < size) { + c = seq[length]; + if (c < 0x80 || c >= 0xC0) { + break; // prevent overrun + } + length++; // add a utf-8 trailer byte + } + } + // Bytes 0xC0 to 0xFD are header bytes in a multibyte sequence. + // The number of one bits above the topmost zero bit indicates the number of bytes (including this one) in the whole sequence. + else if (c < 0xE0) { // 0xC0-0xDF + // add a utf-8 sequence (2 bytes) + if (2 > size) { + return size; // prevent overrun + } + length += 2; + } + else if (c < 0xF0) { // 0xE0-0xEF + // add a utf-8 sequence (3 bytes) + if (3 > size) { + return size; // prevent overrun + } + length += 3; + } + else if (c < 0xF8) { // 0xF0-0xF7 + // add a utf-8 sequence (4 bytes) + if (4 > size) { + return size; // prevent overrun + } + length += 4; + } + else if (c < 0xFC) { // 0xF8-0xFB + // add a utf-8 sequence (5 bytes) + if (5 > size) { + return size; // prevent overrun + } + length += 5; + } + else if (c < 0xFE) { // 0xFC-0xFD + // add a utf-8 sequence (6 bytes) + if (6 > size) { + return size; // prevent overrun + } + length += 6; + } + else { // 0xFE-0xFF + // not a utf-8 sequence + length++; + } + return length; +} + // Encode an UTF-8 string to the local code page. std::string encode_path(const char *src) { diff --git a/src/nanosvg/README.txt b/src/nanosvg/README.txt index 8388aa8ef3..cd38634bec 100644 --- a/src/nanosvg/README.txt +++ b/src/nanosvg/README.txt @@ -1 +1 @@ -Upstream source: https://github.com/memononen/nanosvg/tree/c1f6e209c16b18b46aa9f45d7e619acf42c29726 \ No newline at end of file +Upstream source: https://github.com/fltk/nanosvg/archive/abcd277ea45e9098bed752cf9c6875b533c0892f.zip \ No newline at end of file diff --git a/src/nanosvg/nanosvg.h b/src/nanosvg/nanosvg.h index 57bcb7c2c1..1a1a20cb37 100644 --- a/src/nanosvg/nanosvg.h +++ b/src/nanosvg/nanosvg.h @@ -72,6 +72,7 @@ extern "C" { */ enum NSVGpaintType { + NSVG_PAINT_UNDEF = -1, NSVG_PAINT_NONE = 0, NSVG_PAINT_COLOR = 1, NSVG_PAINT_LINEAR_GRADIENT = 2, @@ -119,7 +120,7 @@ typedef struct NSVGgradient { } NSVGgradient; typedef struct NSVGpaint { - char type; + signed char type; union { unsigned int color; NSVGgradient* gradient; @@ -143,14 +144,17 @@ typedef struct NSVGshape float opacity; // Opacity of the shape. float strokeWidth; // Stroke width (scaled). float strokeDashOffset; // Stroke dash offset (scaled). - float strokeDashArray[8]; // Stroke dash array (scaled). - char strokeDashCount; // Number of dash values in dash array. + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. char strokeLineJoin; // Stroke join type. char strokeLineCap; // Stroke cap type. float miterLimit; // Miter limit char fillRule; // Fill rule, see NSVGfillRule. unsigned char flags; // Logical or of NSVG_FLAGS_* flags float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + char fillGradient[64]; // Optional 'id' of fill gradient + char strokeGradient[64]; // Optional 'id' of stroke gradient + float xform[6]; // Root transformation for fill/stroke gradient NSVGpath* paths; // Linked list of paths in the image. struct NSVGshape* next; // Pointer to next shape, or NULL if last element. } NSVGshape; @@ -181,16 +185,13 @@ void nsvgDelete(NSVGimage* image); #endif #endif -#endif // NANOSVG_H - #ifdef NANOSVG_IMPLEMENTATION #include #include +#include #include -#include - #define NSVG_PI (3.14159265358979323846264338327f) #define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. @@ -227,11 +228,6 @@ static int nsvg__isdigit(char c) return c >= '0' && c <= '9'; } -static int nsvg__isnum(char c) -{ - return strchr("0123456789+-.eE", c) != 0; -} - static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } @@ -402,7 +398,7 @@ typedef struct NSVGgradientData { char id[64]; char ref[64]; - char type; + signed char type; union { NSVGlinearData linear; NSVGradialData radial; @@ -618,7 +614,7 @@ static void nsvg__curveBounds(float* bounds, float* curve) } } -static NSVGparser* nsvg__createParser() +static NSVGparser* nsvg__createParser(void) { NSVGparser* p; p = (NSVGparser*)malloc(sizeof(NSVGparser)); @@ -738,9 +734,11 @@ static void nsvg__lineTo(NSVGparser* p, float x, float y) static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) { - nsvg__addPoint(p, cpx1, cpy1); - nsvg__addPoint(p, cpx2, cpy2); - nsvg__addPoint(p, x, y); + if (p->npts > 0) { + nsvg__addPoint(p, cpx1, cpy1); + nsvg__addPoint(p, cpx2, cpy2); + nsvg__addPoint(p, x, y); + } } static NSVGattrib* nsvg__getAttr(NSVGparser* p) @@ -810,7 +808,9 @@ static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) { NSVGgradientData* grad = p->gradients; - while (grad) { + if (id == NULL || *id == '\0') + return NULL; + while (grad != NULL) { if (strcmp(grad->id, id) == 0) return grad; grad = grad->next; @@ -818,28 +818,34 @@ static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) return NULL; } -static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) +static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, float *xform, signed char* paintType) { - NSVGattrib* attr = nsvg__getAttr(p); NSVGgradientData* data = NULL; NSVGgradientData* ref = NULL; NSVGgradientStop* stops = NULL; NSVGgradient* grad; float ox, oy, sw, sh, sl; int nstops = 0; + int refIter; data = nsvg__findGradientData(p, id); if (data == NULL) return NULL; // TODO: use ref to fill in all unset values too. ref = data; + refIter = 0; while (ref != NULL) { + NSVGgradientData* nextRef = NULL; if (stops == NULL && ref->stops != NULL) { stops = ref->stops; nstops = ref->nstops; break; } - ref = nsvg__findGradientData(p, ref->ref); + nextRef = nsvg__findGradientData(p, ref->ref); + if (nextRef == ref) break; // prevent infite loops on malformed data + ref = nextRef; + refIter++; + if (refIter > 32) break; // prevent infite loops on malformed data } if (stops == NULL) return NULL; @@ -888,7 +894,7 @@ static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const f } nsvg__xformMultiply(grad->xform, data->xform); - nsvg__xformMultiply(grad->xform, attr->xform); + nsvg__xformMultiply(grad->xform, xform); grad->spread = data->spread; memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); @@ -952,6 +958,9 @@ static void nsvg__addShape(NSVGparser* p) memset(shape, 0, sizeof(NSVGshape)); memcpy(shape->id, attr->id, sizeof shape->id); + memcpy(shape->fillGradient, attr->fillGradient, sizeof shape->fillGradient); + memcpy(shape->strokeGradient, attr->strokeGradient, sizeof shape->strokeGradient); + memcpy(shape->xform, attr->xform, sizeof shape->xform); scale = nsvg__getAverageScale(attr->xform); shape->strokeWidth = attr->strokeWidth * scale; shape->strokeDashOffset = attr->strokeDashOffset * scale; @@ -987,13 +996,7 @@ static void nsvg__addShape(NSVGparser* p) shape->fill.color = attr->fillColor; shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; } else if (attr->hasFill == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); - if (shape->fill.gradient == NULL) { - shape->fill.type = NSVG_PAINT_NONE; - } + shape->fill.type = NSVG_PAINT_UNDEF; } // Set stroke @@ -1004,12 +1007,7 @@ static void nsvg__addShape(NSVGparser* p) shape->stroke.color = attr->strokeColor; shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; } else if (attr->hasStroke == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); - if (shape->stroke.gradient == NULL) - shape->stroke.type = NSVG_PAINT_NONE; + shape->stroke.type = NSVG_PAINT_UNDEF; } // Set flags @@ -1042,6 +1040,10 @@ static void nsvg__addPath(NSVGparser* p, char closed) if (closed) nsvg__lineTo(p, p->pts[0], p->pts[1]); + // Expect 1 + N*3 points (N = number of cubic bezier segments). + if ((p->npts % 3) != 1) + return; + path = (NSVGpath*)malloc(sizeof(NSVGpath)); if (path == NULL) goto error; memset(path, 0, sizeof(NSVGpath)); @@ -1090,7 +1092,7 @@ static double nsvg__atof(const char* s) char* cur = (char*)s; char* end = NULL; double res = 0.0, sign = 1.0; - long long intPart = 0, fracPart = 0; + double intPart = 0.0, fracPart = 0.0; char hasIntPart = 0, hasFracPart = 0; // Parse optional sign @@ -1104,9 +1106,13 @@ static double nsvg__atof(const char* s) // Parse integer part if (nsvg__isdigit(*cur)) { // Parse digit sequence +#ifdef _MSC_VER + intPart = (double)_strtoi64(cur, &end, 10); +#else intPart = (double)strtoll(cur, &end, 10); +#endif if (cur != end) { - res = (double)intPart; + res = intPart; hasIntPart = 1; cur = end; } @@ -1117,9 +1123,13 @@ static double nsvg__atof(const char* s) cur++; // Skip '.' if (nsvg__isdigit(*cur)) { // Parse digit sequence - fracPart = strtoll(cur, &end, 10); +#ifdef _MSC_VER + fracPart = (double)_strtoi64(cur, &end, 10); +#else + fracPart = (double)strtoll(cur, &end, 10); +#endif if (cur != end) { - res += (double)fracPart / pow(10.0, (double)(end - cur)); + res += fracPart / pow(10.0, (double)(end - cur)); hasFracPart = 1; cur = end; } @@ -1132,11 +1142,11 @@ static double nsvg__atof(const char* s) // Parse optional exponent if (*cur == 'e' || *cur == 'E') { - int expPart = 0; + double expPart = 0.0; cur++; // skip 'E' - expPart = strtol(cur, &end, 10); // Parse digit sequence with sign + expPart = (double)strtol(cur, &end, 10); // Parse digit sequence with sign if (cur != end) { - res *= pow(10.0, (double)expPart); + res *= pow(10.0, expPart); } } @@ -1170,7 +1180,7 @@ static const char* nsvg__parseNumber(const char* s, char* it, const int size) } } // exponent - if (*s == 'e' || *s == 'E') { + if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { if (i < last) it[i++] = *s; s++; if (*s == '-' || *s == '+') { @@ -1187,6 +1197,19 @@ static const char* nsvg__parseNumber(const char* s, char* it, const int size) return s; } +static const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it) +{ + it[0] = '\0'; + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + if (!*s) return s; + if (*s == '0' || *s == '1') { + it[0] = *s++; + it[1] = '\0'; + return s; + } + return s; +} + static const char* nsvg__getNextPathItem(const char* s, char* it) { it[0] = '\0'; @@ -1207,35 +1230,66 @@ static const char* nsvg__getNextPathItem(const char* s, char* it) static unsigned int nsvg__parseColorHex(const char* str) { - unsigned int c = 0, r = 0, g = 0, b = 0; - int n = 0; - str++; // skip # - // Calculate number of characters. - while(str[n] && !nsvg__isspace(str[n])) - n++; - if (n == 6) { - sscanf(str, "%x", &c); - } else if (n == 3) { - sscanf(str, "%x", &c); - c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); - c |= c<<4; - } - r = (c >> 16) & 0xff; - g = (c >> 8) & 0xff; - b = c & 0xff; - return NSVG_RGB(r,g,b); + unsigned int r=0, g=0, b=0; + if (sscanf(str, "#%2x%2x%2x", &r, &g, &b) == 3 ) // 2 digit hex + return NSVG_RGB(r, g, b); + if (sscanf(str, "#%1x%1x%1x", &r, &g, &b) == 3 ) // 1 digit hex, e.g. #abc -> 0xccbbaa + return NSVG_RGB(r*17, g*17, b*17); // same effect as (r<<4|r), (g<<4|g), .. + return NSVG_RGB(128, 128, 128); } +// Parse rgb color. The pointer 'str' must point at "rgb(" (4+ characters). +// This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors +// for backwards compatibility. Note: other image viewers return black instead. + static unsigned int nsvg__parseColorRGB(const char* str) { - int r = -1, g = -1, b = -1; - char s1[32]="", s2[32]=""; - sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); - if (strchr(s1, '%')) { - return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); - } else { - return NSVG_RGB(r,g,b); + int i; + unsigned int rgbi[3]; + float rgbf[3]; + // try decimal integers first + if (sscanf(str, "rgb(%u, %u, %u)", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) { + // integers failed, try percent values (float, locale independent) + const char delimiter[3] = {',', ',', ')'}; + str += 4; // skip "rgb(" + for (i = 0; i < 3; i++) { + while (*str && (nsvg__isspace(*str))) str++; // skip leading spaces + if (*str == '+') str++; // skip '+' (don't allow '-') + if (!*str) break; + rgbf[i] = nsvg__atof(str); + + // Note 1: it would be great if nsvg__atof() returned how many + // bytes it consumed but it doesn't. We need to skip the number, + // the '%' character, spaces, and the delimiter ',' or ')'. + + // Note 2: The following code does not allow values like "33.%", + // i.e. a decimal point w/o fractional part, but this is consistent + // with other image viewers, e.g. firefox, chrome, eog, gimp. + + while (*str && nsvg__isdigit(*str)) str++; // skip integer part + if (*str == '.') { + str++; + if (!nsvg__isdigit(*str)) break; // error: no digit after '.' + while (*str && nsvg__isdigit(*str)) str++; // skip fractional part + } + if (*str == '%') str++; else break; + while (nsvg__isspace(*str)) str++; + if (*str == delimiter[i]) str++; + else break; + } + if (i == 3) { + rgbi[0] = roundf(rgbf[0] * 2.55f); + rgbi[1] = roundf(rgbf[1] * 2.55f); + rgbi[2] = roundf(rgbf[2] * 2.55f); + } else { + rgbi[0] = rgbi[1] = rgbi[2] = 128; + } } + // clip values as the CSS spec requires + for (i = 0; i < 3; i++) { + if (rgbi[i] > 255) rgbi[i] = 255; + } + return NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]); } typedef struct NSVGNamedColor { @@ -1460,6 +1514,15 @@ static int nsvg__parseUnits(const char* units) return NSVG_UNITS_USER; } +static int nsvg__isCoordinate(const char* s) +{ + // optional sign + if (*s == '-' || *s == '+') + s++; + // must have at least one digit, or start by a dot + return (nsvg__isdigit(*s) || *s == '.'); +} + static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) { NSVGcoordinate coord = {0, NSVG_UNITS_USER}; @@ -1599,25 +1662,32 @@ static int nsvg__parseRotate(float* xform, const char* str) static void nsvg__parseTransform(float* xform, const char* str) { float t[6]; + int len; nsvg__xformIdentity(xform); while (*str) { if (strncmp(str, "matrix", 6) == 0) - str += nsvg__parseMatrix(t, str); + len = nsvg__parseMatrix(t, str); else if (strncmp(str, "translate", 9) == 0) - str += nsvg__parseTranslate(t, str); + len = nsvg__parseTranslate(t, str); else if (strncmp(str, "scale", 5) == 0) - str += nsvg__parseScale(t, str); + len = nsvg__parseScale(t, str); else if (strncmp(str, "rotate", 6) == 0) - str += nsvg__parseRotate(t, str); + len = nsvg__parseRotate(t, str); else if (strncmp(str, "skewX", 5) == 0) - str += nsvg__parseSkewX(t, str); + len = nsvg__parseSkewX(t, str); else if (strncmp(str, "skewY", 5) == 0) - str += nsvg__parseSkewY(t, str); + len = nsvg__parseSkewY(t, str); else{ ++str; continue; } + if (len != 0) { + str += len; + } else { + ++str; + continue; + } nsvg__xformPremultiply(xform, t); } @@ -1627,9 +1697,9 @@ static void nsvg__parseUrl(char* id, const char* str) { int i = 0; str += 4; // "url("; - if (*str == '#') + if (*str && *str == '#') str++; - while (i < 63 && *str != ')') { + while (i < 63 && *str && *str != ')') { id[i] = *str++; i++; } @@ -1878,8 +1948,11 @@ static int nsvg__getArgsPerElement(char cmd) case 'a': case 'A': return 7; + case 'z': + case 'Z': + return 0; } - return 0; + return -1; } static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) @@ -2160,7 +2233,12 @@ static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, // The loop assumes an iteration per end point (including start and end), this +1. ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); hda = (da / (float)ndivs) / 2.0f; - kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + // Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite) + if ((hda < 1e-3f) && (hda > -1e-3f)) + hda *= 0.5f; + else + hda = (1.0f - cosf(hda)) / sinf(hda); + kappa = fabsf(4.0f / 3.0f * hda); if (da < 0.0f) kappa = -kappa; @@ -2189,6 +2267,7 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) float args[10]; int nargs; int rargs = 0; + char initPoint; float cpx, cpy, cpx2, cpy2; const char* tmp[4]; char closedFlag; @@ -2211,13 +2290,18 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) nsvg__resetPath(p); cpx = 0; cpy = 0; cpx2 = 0; cpy2 = 0; + initPoint = 0; closedFlag = 0; nargs = 0; while (*s) { - s = nsvg__getNextPathItem(s, item); + item[0] = '\0'; + if ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4)) + s = nsvg__getNextPathItemWhenArcFlag(s, item); + if (!*item) + s = nsvg__getNextPathItem(s, item); if (!*item) break; - if (nsvg__isnum(item[0])) { + if (cmd != '\0' && nsvg__isCoordinate(item)) { if (nargs < 10) args[nargs++] = (float)nsvg__atof(item); if (nargs >= rargs) { @@ -2230,6 +2314,7 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) cmd = (cmd == 'm') ? 'l' : 'L'; rargs = nsvg__getArgsPerElement(cmd); cpx2 = cpx; cpy2 = cpy; + initPoint = 1; break; case 'l': case 'L': @@ -2279,7 +2364,6 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) } } else { cmd = item[0]; - rargs = nsvg__getArgsPerElement(cmd); if (cmd == 'M' || cmd == 'm') { // Commit path. if (p->npts > 0) @@ -2288,7 +2372,11 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) nsvg__resetPath(p); closedFlag = 0; nargs = 0; - } else if (cmd == 'Z' || cmd == 'z') { + } else if (initPoint == 0) { + // Do not allow other commands until initial point has been set (moveTo called once). + cmd = '\0'; + } + if (cmd == 'Z' || cmd == 'z') { closedFlag = 1; // Commit path. if (p->npts > 0) { @@ -2304,6 +2392,12 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) closedFlag = 0; nargs = 0; } + rargs = nsvg__getArgsPerElement(cmd); + if (rargs == -1) { + // Command not recognized + cmd = '\0'; + rargs = 0; + } } } // Commit path. @@ -2550,7 +2644,7 @@ static void nsvg__parseSVG(NSVGparser* p, const char** attr) } } -static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) +static void nsvg__parseGradient(NSVGparser* p, const char** attr, signed char type) { int i; NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); @@ -2875,6 +2969,36 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) } } +static void nsvg__createGradients(NSVGparser* p) +{ + NSVGshape* shape; + + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + if (shape->fill.type == NSVG_PAINT_UNDEF) { + if (shape->fillGradient[0] != '\0') { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, shape->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->fill.gradient = nsvg__createGradient(p, shape->fillGradient, localBounds, shape->xform, &shape->fill.type); + } + if (shape->fill.type == NSVG_PAINT_UNDEF) { + shape->fill.type = NSVG_PAINT_NONE; + } + } + if (shape->stroke.type == NSVG_PAINT_UNDEF) { + if (shape->strokeGradient[0] != '\0') { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, shape->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->stroke.gradient = nsvg__createGradient(p, shape->strokeGradient, localBounds, shape->xform, &shape->stroke.type); + } + if (shape->stroke.type == NSVG_PAINT_UNDEF) { + shape->stroke.type = NSVG_PAINT_NONE; + } + } + } +} + NSVGimage* nsvgParse(char* input, const char* units, float dpi) { NSVGparser* p; @@ -2888,6 +3012,9 @@ NSVGimage* nsvgParse(char* input, const char* units, float dpi) nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); + // Create gradients after all definitions have been parsed + nsvg__createGradients(p); + // Scale to viewBox nsvg__scaleToViewbox(p, units); @@ -2899,8 +3026,6 @@ NSVGimage* nsvgParse(char* input, const char* units, float dpi) return ret; } -#include - NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) { FILE* fp = NULL; @@ -2908,8 +3033,8 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) char* data = NULL; NSVGimage* image = NULL; - fp = boost::nowide::fopen(filename, "rb"); - if (!fp) goto error; + fp = fopen(filename, "rb"); + if (!fp) goto error; fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); @@ -2918,9 +3043,9 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) if (fread(data, 1, size, fp) != size) goto error; data[size] = '\0'; // Must be null terminated. fclose(fp); - image = nsvgParse(data, units, dpi); free(data); + return image; error: @@ -2976,4 +3101,6 @@ void nsvgDelete(NSVGimage* image) free(image); } -#endif +#endif // NANOSVG_IMPLEMENTATION + +#endif // NANOSVG_H diff --git a/src/nanosvg/nanosvgrast.h b/src/nanosvg/nanosvgrast.h index b740c316ca..a83db27260 100644 --- a/src/nanosvg/nanosvgrast.h +++ b/src/nanosvg/nanosvgrast.h @@ -22,9 +22,17 @@ * */ +/* Modified by FLTK to support non-square X,Y axes scaling. + * + * Added: nsvgRasterizeXY() +*/ + + #ifndef NANOSVGRAST_H #define NANOSVGRAST_H +#include "nanosvg.h" + #ifndef NANOSVGRAST_CPLUSPLUS #ifdef __cplusplus extern "C" { @@ -44,16 +52,19 @@ typedef struct NSVGrasterizer NSVGrasterizer; unsigned char* img = malloc(w*h*4); // Rasterize nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); + + // For non-square X,Y scaling, use + nsvgRasterizeXY(rast, image, 0,0,1,1, img, w, h, w*4); */ // Allocated rasterizer context. -NSVGrasterizer* nsvgCreateRasterizer(); +NSVGrasterizer* nsvgCreateRasterizer(void); // Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) // r - pointer to rasterizer context // image - pointer to image to rasterize // tx,ty - image offset (applied after scaling) -// scale - image scale +// scale - image scale (assumes square aspect ratio) // dst - pointer to destination image data, 4 bytes per pixel (RGBA) // w - width of the image to render // h - height of the image to render @@ -62,6 +73,12 @@ void nsvgRasterize(NSVGrasterizer* r, NSVGimage* image, float tx, float ty, float scale, unsigned char* dst, int w, int h, int stride); +// As above, but allow X and Y axes to scale independently for non-square aspects +void nsvgRasterizeXY(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, + float sx, float sy, + unsigned char* dst, int w, int h, int stride); + // Deletes rasterizer context. void nsvgDeleteRasterizer(NSVGrasterizer*); @@ -72,11 +89,11 @@ void nsvgDeleteRasterizer(NSVGrasterizer*); #endif #endif -#endif // NANOSVGRAST_H - #ifdef NANOSVGRAST_IMPLEMENTATION #include +#include +#include #define NSVG__SUBSAMPLES 5 #define NSVG__FIXSHIFT 10 @@ -112,7 +129,7 @@ typedef struct NSVGmemPage { } NSVGmemPage; typedef struct NSVGcachedPaint { - char type; + signed char type; char spread; float xform[6]; unsigned int colors[256]; @@ -148,7 +165,7 @@ struct NSVGrasterizer int width, height, stride; }; -NSVGrasterizer* nsvgCreateRasterizer() +NSVGrasterizer* nsvgCreateRasterizer(void) { NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); if (r == NULL) goto error; @@ -368,7 +385,7 @@ static void nsvg__flattenCubicBez(NSVGrasterizer* r, nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); } -static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) +static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float sx, float sy) { int i, j; NSVGpath* path; @@ -376,13 +393,13 @@ static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) for (path = shape->paths; path != NULL; path = path->next) { r->npoints = 0; // Flatten path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, 0); for (i = 0; i < path->npts-1; i += 3) { float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + nsvg__flattenCubicBez(r, p[0]*sx,p[1]*sy, p[2]*sx,p[3]*sy, p[4]*sx,p[5]*sy, p[6]*sx,p[7]*sy, 0, 0); } // Close path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, 0); // Build edges for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); @@ -732,7 +749,7 @@ static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoi } } -static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) +static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float sx, float sy) { int i, j, closed; NSVGpath* path; @@ -740,15 +757,16 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float float miterLimit = shape->miterLimit; int lineJoin = shape->strokeLineJoin; int lineCap = shape->strokeLineCap; - float lineWidth = shape->strokeWidth * scale; + const float sw = (sx + sy) / 2; // average scaling factor + const float lineWidth = shape->strokeWidth * sw; // FIXME (?) for (path = shape->paths; path != NULL; path = path->next) { // Flatten path r->npoints = 0; - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); + nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, NSVG_PT_CORNER); for (i = 0; i < path->npts-1; i += 3) { float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); + nsvg__flattenCubicBez(r, p[0]*sx,p[1]*sy, p[2]*sx,p[3]*sy, p[4]*sx,p[5]*sy, p[6]*sx,p[7]*sy, 0, NSVG_PT_CORNER); } if (r->npoints < 2) continue; @@ -794,7 +812,7 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float dashOffset -= shape->strokeDashArray[idash]; idash = (idash + 1) % shape->strokeDashCount; } - dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + dashLen = (shape->strokeDashArray[idash] - dashOffset) * sw; for (j = 1; j < r->npoints2; ) { float dx = r->points2[j].x - cur.x; @@ -816,7 +834,7 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float // Advance dash pattern dashState = !dashState; idash = (idash+1) % shape->strokeDashCount; - dashLen = shape->strokeDashArray[idash] * scale; + dashLen = shape->strokeDashArray[idash] * sw; // Restart cur.x = x; cur.y = y; @@ -956,7 +974,7 @@ static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { - return (r) | (g << 8) | (b << 16) | (a << 24); + return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24); } static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) @@ -985,7 +1003,7 @@ static inline int nsvg__div255(int x) } static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, - float tx, float ty, float scale, NSVGcachedPaint* cache) + float tx, float ty, float sx, float sy, NSVGcachedPaint* cache) { if (cache->type == NSVG_PAINT_COLOR) { @@ -1026,9 +1044,9 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co int i, cr, cg, cb, ca; unsigned int c; - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; + fx = ((float)x - tx) / sx; + fy = ((float)y - ty) / sy; + dx = 1.0f / sx; for (i = 0; i < count; i++) { int r,g,b,a,ia; @@ -1071,9 +1089,9 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co int i, cr, cg, cb, ca; unsigned int c; - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; + fx = ((float)x - tx) / sx; + fy = ((float)y - ty) / sy; + dx = 1.0f / sx; for (i = 0; i < count; i++) { int r,g,b,a,ia; @@ -1112,7 +1130,7 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co } } -static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) +static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float sx, float sy, NSVGcachedPaint* cache, char fillRule) { NSVGactiveEdge *active = NULL; int y, s; @@ -1194,7 +1212,7 @@ static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, fl if (xmin < 0) xmin = 0; if (xmax > r->width-1) xmax = r->width-1; if (xmin <= xmax) { - nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); + nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, sx, sy, cache); } } @@ -1362,8 +1380,9 @@ static void dumpEdges(NSVGrasterizer* r, const char* name) } */ -void nsvgRasterize(NSVGrasterizer* r, - NSVGimage* image, float tx, float ty, float scale, +void nsvgRasterizeXY(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, + float sx, float sy, unsigned char* dst, int w, int h, int stride) { NSVGshape *shape = NULL; @@ -1394,7 +1413,7 @@ void nsvgRasterize(NSVGrasterizer* r, r->freelist = NULL; r->nedges = 0; - nsvg__flattenShape(r, shape, scale); + nsvg__flattenShape(r, shape, sx, sy); // Scale and translate edges for (i = 0; i < r->nedges; i++) { @@ -1406,19 +1425,20 @@ void nsvgRasterize(NSVGrasterizer* r, } // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + if (r->nedges != 0) + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule nsvg__initPaint(&cache, &shape->fill, shape->opacity); - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + nsvg__rasterizeSortedEdges(r, tx,ty, sx, sy, &cache, shape->fillRule); } - if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * sx) > 0.01f) { nsvg__resetPool(r); r->freelist = NULL; r->nedges = 0; - nsvg__flattenShapeStroke(r, shape, scale); + nsvg__flattenShapeStroke(r, shape, sx, sy); // dumpEdges(r, "edge.svg"); @@ -1432,12 +1452,13 @@ void nsvgRasterize(NSVGrasterizer* r, } // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + if (r->nedges != 0) + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule nsvg__initPaint(&cache, &shape->stroke, shape->opacity); - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + nsvg__rasterizeSortedEdges(r, tx,ty,sx, sy, &cache, NSVG_FILLRULE_NONZERO); } } @@ -1449,4 +1470,13 @@ void nsvgRasterize(NSVGrasterizer* r, r->stride = 0; } -#endif +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) +{ + nsvgRasterizeXY(r,image, tx, ty, scale, scale, dst, w, h, stride); +} + +#endif // NANOSVGRAST_IMPLEMENTATION + +#endif // NANOSVGRAST_H diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 61dc8c9491..173b0ce012 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -1,3 +1,12 @@ +#/|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Oleksandra Iushchenko @YuSanka, Lukáš Hejl @hejllukas, Vojtěch Král @vojtechkral +#/|/ Copyright (c) 2023 Pedro Lamas @PedroLamas +#/|/ Copyright (c) 2020 Sergey Kovalev @RandoMan70 +#/|/ Copyright (c) 2021 Boleslaw Ciesielski +#/|/ Copyright (c) 2019 Spencer Owen @spuder +#/|/ Copyright (c) 2019 Stephan Reichhelm @stephanr +#/|/ +#/|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +#/|/ cmake_minimum_required(VERSION 3.13) project(libslic3r_gui) @@ -99,6 +108,8 @@ set(SLIC3R_GUI_SOURCES GUI/GLShader.hpp GUI/GLCanvas3D.hpp GUI/GLCanvas3D.cpp + GUI/SceneRaycaster.hpp + GUI/SceneRaycaster.cpp GUI/OpenGLManager.hpp GUI/OpenGLManager.cpp GUI/Selection.hpp @@ -115,24 +126,26 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoRotate.hpp GUI/Gizmos/GLGizmoScale.cpp GUI/Gizmos/GLGizmoScale.hpp - GUI/Gizmos/GLGizmoSlaSupports.cpp - GUI/Gizmos/GLGizmoSlaSupports.hpp + #GUI/Gizmos/GLGizmoSlaSupports.cpp + #GUI/Gizmos/GLGizmoSlaSupports.hpp GUI/Gizmos/GLGizmoFdmSupports.cpp GUI/Gizmos/GLGizmoFdmSupports.hpp GUI/Gizmos/GLGizmoFlatten.cpp GUI/Gizmos/GLGizmoFlatten.hpp - GUI/Gizmos/GLGizmoAdvancedCut.cpp - GUI/Gizmos/GLGizmoAdvancedCut.hpp - GUI/Gizmos/GLGizmoHollow.cpp - GUI/Gizmos/GLGizmoHollow.hpp + GUI/Gizmos/GLGizmoCut.cpp + GUI/Gizmos/GLGizmoCut.hpp + #GUI/Gizmos/GLGizmoHollow.cpp + #GUI/Gizmos/GLGizmoHollow.hpp GUI/Gizmos/GLGizmoPainterBase.cpp GUI/Gizmos/GLGizmoPainterBase.hpp GUI/Gizmos/GLGizmoSimplify.cpp GUI/Gizmos/GLGizmoSimplify.hpp GUI/Gizmos/GLGizmoMmuSegmentation.cpp GUI/Gizmos/GLGizmoMmuSegmentation.hpp - GUI/Gizmos/GLGizmoFaceDetector.cpp - GUI/Gizmos/GLGizmoFaceDetector.hpp + #GUI/Gizmos/GLGizmoFaceDetector.cpp + #GUI/Gizmos/GLGizmoFaceDetector.hpp + GUI/Gizmos/GLGizmoMeasure.cpp + GUI/Gizmos/GLGizmoMeasure.hpp GUI/Gizmos/GLGizmoSeam.cpp GUI/Gizmos/GLGizmoSeam.hpp GUI/Gizmos/GLGizmoText.cpp @@ -173,6 +186,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_App.hpp GUI/GUI_Utils.cpp GUI/GUI_Utils.hpp + GUI/GUI_Geometry.cpp + GUI/GUI_Geometry.hpp GUI/I18N.cpp GUI/I18N.hpp GUI/MainFrame.cpp @@ -348,7 +363,6 @@ set(SLIC3R_GUI_SOURCES GUI/Mouse3DController.hpp GUI/IMSlider.cpp GUI/IMSlider.hpp - GUI/IMSlider_Utils.hpp GUI/Notebook.cpp GUI/Notebook.hpp GUI/TabButton.cpp diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index a7b6e050eb..88c61ac8b1 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -12,6 +12,8 @@ #include "GUI_App.hpp" #include "GUI_Colors.hpp" #include "GLCanvas3D.hpp" +#include "Plater.hpp" +#include "Camera.hpp" #include @@ -26,13 +28,58 @@ #endif static const float GROUND_Z = -0.04f; -static const std::array DEFAULT_MODEL_COLOR = { 0.3255f, 0.337f, 0.337f, 1.0f }; -static const std::array DEFAULT_MODEL_COLOR_DARK = { 0.255f, 0.255f, 0.283f, 1.0f }; -static const std::array PICKING_MODEL_COLOR = { 0.0f, 0.0f, 0.0f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_MODEL_COLOR = { 0.3255f, 0.337f, 0.337f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_MODEL_COLOR_DARK = { 0.255f, 0.255f, 0.283f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_SOLID_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f }; +static const Slic3r::ColorRGBA DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 0.6f }; namespace Slic3r { namespace GUI { +bool init_model_from_poly(GLModel &model, const ExPolygon &poly, float z) +{ + if (poly.empty()) + return false; + + const std::vector triangles = triangulate_expolygon_2f(poly, NORMALS_UP); + if (triangles.empty() || triangles.size() % 3 != 0) + return false; + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2 }; + init_data.reserve_vertices(triangles.size()); + init_data.reserve_indices(triangles.size() / 3); + + Vec2f min = triangles.front(); + Vec2f max = min; + for (const Vec2f &v : triangles) { + min = min.cwiseMin(v).eval(); + max = max.cwiseMax(v).eval(); + } + + const Vec2f size = max - min; + if (size.x() <= 0.0f || size.y() <= 0.0f) + return false; + + Vec2f inv_size = size.cwiseInverse(); + inv_size.y() *= -1.0f; + + // vertices + indices + unsigned int vertices_counter = 0; + for (const Vec2f &v : triangles) { + const Vec3f p = {v.x(), v.y(), z}; + init_data.add_vertex(p, (Vec2f)(v - min).cwiseProduct(inv_size).eval()); + ++vertices_counter; + if (vertices_counter % 3 == 0) + init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + + model.init_from(std::move(init_data)); + + return true; +} + +/* bool GeometryBuffer::set_from_triangles(const std::vector &triangles, float z) { if (triangles.empty()) { @@ -131,41 +178,45 @@ const float* GeometryBuffer::get_vertices_data() const { return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; } +*/ const float Bed3D::Axes::DefaultStemRadius = 0.5f; const float Bed3D::Axes::DefaultStemLength = 25.0f; const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; const float Bed3D::Axes::DefaultTipLength = 5.0f; -std::array Bed3D::AXIS_X_COLOR = decode_color_to_float_array("#FF0000"); -std::array Bed3D::AXIS_Y_COLOR = decode_color_to_float_array("#00FF00"); -std::array Bed3D::AXIS_Z_COLOR = decode_color_to_float_array("#0000FF"); +ColorRGBA Bed3D::AXIS_X_COLOR = ColorRGBA::X(); +ColorRGBA Bed3D::AXIS_Y_COLOR = ColorRGBA::Y(); +ColorRGBA Bed3D::AXIS_Z_COLOR = ColorRGBA::Z(); void Bed3D::update_render_colors() { - Bed3D::AXIS_X_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_X]); - Bed3D::AXIS_Y_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_Y]); - Bed3D::AXIS_Z_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_Z]); + Bed3D::AXIS_X_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Axis_X]); + Bed3D::AXIS_Y_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Axis_Y]); + Bed3D::AXIS_Z_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Axis_Z]); } void Bed3D::load_render_colors() { - RenderColor::colors[RenderCol_Axis_X] = IMColor(Bed3D::AXIS_X_COLOR); - RenderColor::colors[RenderCol_Axis_Y] = IMColor(Bed3D::AXIS_Y_COLOR); - RenderColor::colors[RenderCol_Axis_Z] = IMColor(Bed3D::AXIS_Z_COLOR); + RenderColor::colors[RenderCol_Axis_X] = ImGuiWrapper::to_ImVec4(Bed3D::AXIS_X_COLOR); + RenderColor::colors[RenderCol_Axis_Y] = ImGuiWrapper::to_ImVec4(Bed3D::AXIS_Y_COLOR); + RenderColor::colors[RenderCol_Axis_Z] = ImGuiWrapper::to_ImVec4(Bed3D::AXIS_Z_COLOR); } -void Bed3D::Axes::render() const +void Bed3D::Axes::render() { - auto render_axis = [this](const Transform3f& transform) { - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixf(transform.data())); + auto render_axis = [this](GLShaderProgram* shader, const Transform3d& transform) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * transform); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * transform.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); m_arrow.render(); - glsafe(::glPopMatrix()); }; if (!m_arrow.is_initialized()) - const_cast(&m_arrow)->init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); + m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -177,16 +228,16 @@ void Bed3D::Axes::render() const shader->set_uniform("emission_factor", 0.0f); // x axis - const_cast(&m_arrow)->set_color(-1, AXIS_X_COLOR); - render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); + m_arrow.set_color(AXIS_X_COLOR); + render_axis(shader, Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); // y axis - const_cast(&m_arrow)->set_color(-1, AXIS_Y_COLOR); - render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); + m_arrow.set_color(AXIS_Y_COLOR); + render_axis(shader, Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); // z axis - const_cast(&m_arrow)->set_color(-1, AXIS_Z_COLOR); - render_axis(Geometry::assemble_transform(m_origin).cast()); + m_arrow.set_color(AXIS_Z_COLOR); + render_axis(shader, Geometry::assemble_transform(m_origin)); shader->stop_using(); @@ -259,25 +310,9 @@ bool Bed3D::set_shape(const Pointfs& printable_area, const double printable_heig //BBS: add part plate logic //BBS add default bed -#if 1 - ExPolygon poly{ Polygon::new_scale(printable_area) }; -#else - ExPolygon poly; - for (const Vec2d& p : printable_area) { - poly.contour.append(Point(scale_(p(0) + m_position.x()), scale_(p(1) + m_position.y()))); - } -#endif - - calc_triangles(poly); - - //no need gridline for 3dbed - //const BoundingBox& bed_bbox = poly.contour.bounding_box(); - //calc_gridlines(poly, bed_bbox); - - //m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0]; + m_triangles.reset(); if (with_reset) { - this->release_VBOs(); //m_texture.reset(); m_model.reset(); } @@ -290,6 +325,10 @@ bool Bed3D::set_shape(const Pointfs& printable_area, const double printable_heig m_axes.set_origin({ 0.0, 0.0, static_cast(GROUND_Z) }); m_axes.set_stem_length(0.1f * static_cast(m_build_volume.bounding_volume().max_size())); + // unregister from picking + // BBS: remove the bed picking logic + // wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed); + // Let the calee to update the UI. return true; } @@ -325,33 +364,28 @@ void Bed3D::on_change_color_mode(bool is_dark) m_is_dark = is_dark; } -void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) +void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes) { - render_internal(canvas, bottom, scale_factor, show_axes); + render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_axes); } -/*void Bed3D::render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor) -{ - render_internal(canvas, bottom, scale_factor, false, false, true); -}*/ - -void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, +void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes) { - float* factor = const_cast(&m_scale_factor); - *factor = scale_factor; + m_scale_factor = scale_factor; if (show_axes) render_axes(); + glsafe(::glEnable(GL_DEPTH_TEST)); - m_model.set_color(-1, m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); + m_model.set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); switch (m_type) { - case Type::System: { render_system(canvas, bottom); break; } + case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom); break; } default: - case Type::Custom: { render_custom(canvas, bottom); break; } + case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom); break; } } glsafe(::glDisable(GL_DEPTH_TEST)); @@ -388,39 +422,6 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box(bool consider_model_offset) cons return out; } -void Bed3D::calc_triangles(const ExPolygon& poly) -{ - if (! m_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << "Unable to create bed triangles"; -} - -void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) -{ - /*Polylines axes_lines; - for (coord_t x = bed_bbox.min.x(); x <= bed_bbox.max.x(); x += scale_(10.0)) { - Polyline line; - line.append(Point(x, bed_bbox.min.y())); - line.append(Point(x, bed_bbox.max.y())); - axes_lines.push_back(line); - } - for (coord_t y = bed_bbox.min.y(); y <= bed_bbox.max.y(); y += scale_(10.0)) { - Polyline line; - line.append(Point(bed_bbox.min.x(), y)); - line.append(Point(bed_bbox.max.x(), y)); - axes_lines.push_back(line); - } - - // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped - Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, (float)SCALED_EPSILON))); - - // append bed contours - Lines contour_lines = to_lines(poly); - std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines)); - - if (!m_gridlines.set_from_lines(gridlines, GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << "Unable to create bed grid lines\n";*/ -} - // Try to match the print bed shape with the shape of an active profile. If such a match exists, // return the print bed model. std::tuple Bed3D::detect_type(const Pointfs& shape) @@ -454,22 +455,22 @@ std::tuple Bed3D::detect_type(const Point return { Type::Custom, {}, {} }; } -void Bed3D::render_axes() const +void Bed3D::render_axes() { if (m_build_volume.valid()) m_axes.render(); } -void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const +void Bed3D::render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) { if (!bottom) - render_model(); + render_model(view_matrix, projection_matrix); /*if (show_texture) render_texture(bottom, canvas);*/ } -/*void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const +/*void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) { GLTexture* texture = const_cast(&m_texture); GLTexture* temp_texture = const_cast(&m_temp_texture); @@ -537,6 +538,9 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const GLShaderProgram* shader = wxGetApp().get_shader("printbed"); if (shader != nullptr) { shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("transparent_background", bottom); shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); @@ -624,9 +628,10 @@ void Bed3D::update_model_offset() const const_cast(m_extended_bounding_box) = calc_extended_bounding_box(); } -GeometryBuffer Bed3D::update_bed_triangles() const +void Bed3D::update_bed_triangles() { - GeometryBuffer new_triangles; + m_triangles.reset(); + Vec3d shift = m_extended_bounding_box.center(); shift(2) = -0.03; Vec3d* model_offset_ptr = const_cast(&m_model_offset); @@ -634,7 +639,7 @@ GeometryBuffer Bed3D::update_bed_triangles() const //BBS: TODO: hack for default bed BoundingBoxf3 build_volume; - if (!m_build_volume.valid()) return new_triangles; + if (!m_build_volume.valid()) return; auto bed_ext = get_extents(m_bed_shape); (*model_offset_ptr)(0) = m_build_volume.bounding_volume2d().min.x() - bed_ext.min.x(); (*model_offset_ptr)(1) = m_build_volume.bounding_volume2d().min.y() - bed_ext.min.y(); @@ -646,105 +651,115 @@ GeometryBuffer Bed3D::update_bed_triangles() const new_bed_shape.push_back(new_point); } ExPolygon poly{ Polygon::new_scale(new_bed_shape) }; - if (!new_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) { - ; + if (!init_model_from_poly(m_triangles, poly, GROUND_Z)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to update plate triangles\n"; } // update extended bounding box const_cast(m_extended_bounding_box) = calc_extended_bounding_box(); - return new_triangles; } -void Bed3D::render_model() const +void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix) { if (m_model_filename.empty()) return; - GLModel* model = const_cast(&m_model); - - if (model->get_filename() != m_model_filename && model->init_from_file(m_model_filename)) { - model->set_color(-1, m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); + if (m_model.get_filename() != m_model_filename && m_model.init_from_file(m_model_filename)) { + m_model.set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); update_model_offset(); + + // BBS: remove the bed picking logic + //register_raycasters_for_picking(m_model.model.get_geometry(), Geometry::assemble_transform(m_model_offset)); } - if (!model->get_filename().empty()) { + if (!m_model.get_filename().empty()) { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.0f); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z())); - model->render(); - glsafe(::glPopMatrix()); + const Transform3d model_matrix = Geometry::assemble_transform(m_model_offset); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + m_model.render(); shader->stop_using(); } } } -void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const +void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) { if (m_model_filename.empty()) { - render_default(bottom); + render_default(bottom, view_matrix, projection_matrix); return; } if (!bottom) - render_model(); + render_model(view_matrix, projection_matrix); /*if (show_texture) render_texture(bottom, canvas);*/ } -void Bed3D::render_default(bool bottom) const +void Bed3D::render_default(bool bottom, const Transform3d& view_matrix, const Transform3d& projection_matrix) { - bool picking = false; - const_cast(&m_texture)->reset(); + m_texture.reset(); - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - GeometryBuffer default_triangles = update_bed_triangles(); - if (triangles_vcount > 0) { - bool has_model = !m_model.get_filename().empty(); + update_bed_triangles(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + shader->set_uniform("view_model_matrix", view_matrix); + shader->set_uniform("projection_matrix", projection_matrix); glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - if (!has_model && !bottom) { + if (m_model.get_filename().empty() && !bottom) { // draw background glsafe(::glDepthMask(GL_FALSE)); - glsafe(::glColor4fv(picking ? PICKING_MODEL_COLOR.data() : DEFAULT_MODEL_COLOR.data())); - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, default_triangles.get_vertex_data_size(), (GLvoid*)default_triangles.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + m_triangles.set_color(DEFAULT_MODEL_COLOR); + m_triangles.render(); glsafe(::glDepthMask(GL_TRUE)); } /*if (!picking) { // draw grid glsafe(::glLineWidth(1.5f * m_scale_factor)); - if (has_model && !bottom) - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 1.0f)); - else - glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.6f)); - glsafe(::glVertexPointer(3, GL_FLOAT, default_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); + m_gridlines.set_color(picking ? DEFAULT_SOLID_GRID_COLOR : DEFAULT_TRANSPARENT_GRID_COLOR); + m_gridlines.render(); }*/ - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisable(GL_BLEND)); + + shader->stop_using(); } } -void Bed3D::release_VBOs() +// BBS: remove the bed picking logic +/* +void Bed3D::register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo) { - if (m_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_vbo_id)); - m_vbo_id = 0; - } -} + assert(m_model.mesh_raycaster == nullptr); + indexed_triangle_set its; + its.vertices.reserve(geometry.vertices_count()); + for (size_t i = 0; i < geometry.vertices_count(); ++i) { + its.vertices.emplace_back(geometry.extract_position_3(i)); + } + its.indices.reserve(geometry.indices_count() / 3); + for (size_t i = 0; i < geometry.indices_count() / 3; ++i) { + const size_t tri_id = i * 3; + its.indices.emplace_back(geometry.extract_index(tri_id), geometry.extract_index(tri_id + 1), geometry.extract_index(tri_id + 2)); + } + + m_model.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + wxGetApp().plater()->canvas3D()->add_raycaster_for_picking(SceneRaycaster::EType::Bed, 0, *m_model.mesh_raycaster, trafo); +} +*/ } // GUI } // Slic3r diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index f6daadddf1..b668b5ed61 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -5,7 +5,8 @@ #include "3DScene.hpp" #include "GLModel.hpp" -#include +#include "libslic3r/BuildVolume.hpp" +#include "libslic3r/ExPolygon.hpp" #include #include @@ -15,6 +16,7 @@ namespace GUI { class GLCanvas3D; +/* class GeometryBuffer { struct Vertex @@ -38,13 +40,16 @@ public: size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } }; +*/ + +bool init_model_from_poly(GLModel &model, const ExPolygon &poly, float z); class Bed3D { public: - static std::array AXIS_X_COLOR; - static std::array AXIS_Y_COLOR; - static std::array AXIS_Z_COLOR; + static ColorRGBA AXIS_X_COLOR; + static ColorRGBA AXIS_Y_COLOR; + static ColorRGBA AXIS_Z_COLOR; static void update_render_colors(); static void load_render_colors(); @@ -70,7 +75,7 @@ public: m_arrow.reset(); } float get_total_length() const { return m_stem_length + DefaultTipLength; } - void render() const; + void render(); }; public: @@ -91,14 +96,13 @@ private: BoundingBoxf3 m_extended_bounding_box; // Slightly expanded print bed polygon, for collision detection. //Polygon m_polygon; - GeometryBuffer m_triangles; - //GeometryBuffer m_gridlines; + GLModel m_triangles; + //GLModel m_gridlines; GLTexture m_texture; // temporary texture shown until the main texture has still no levels compressed //GLTexture m_temp_texture; GLModel m_model; Vec3d m_model_offset{ Vec3d::Zero() }; - unsigned int m_vbo_id{ 0 }; Axes m_axes; float m_scale_factor{ 1.0f }; @@ -109,7 +113,7 @@ private: public: Bed3D() = default; - ~Bed3D() { release_VBOs(); } + ~Bed3D() = default; // Update print bed model from configuration. // Return true if the bed shape changed, so the calee will update the UI. @@ -142,8 +146,7 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes); - //void render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor); + void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes); void on_change_color_mode(bool is_dark); @@ -151,21 +154,21 @@ private: //BBS: add partplate related logic // Calculate an extended bounding box from axes and current model for visualization purposes. BoundingBoxf3 calc_extended_bounding_box(bool consider_model_offset = true) const; - void calc_triangles(const ExPolygon& poly); - void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); void update_model_offset() const; //BBS: with offset - GeometryBuffer update_bed_triangles() const; + void update_bed_triangles(); static std::tuple detect_type(const Pointfs& shape); - void render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, + void render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes); - void render_axes() const; - void render_system(GLCanvas3D& canvas, bool bottom) const; - //void render_texture(bool bottom, GLCanvas3D& canvas) const; - void render_model() const; - void render_custom(GLCanvas3D& canvas, bool bottom) const; - void render_default(bool bottom) const; - void release_VBOs(); + void render_axes(); + void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); + //void render_texture(bool bottom, GLCanvas3D& canvas); + void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix); + void render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); + void render_default(bool bottom, const Transform3d& view_matrix, const Transform3d& projection_matrix); + + // BBS: remove the bed picking logic + // void register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo); }; } // GUI diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index be821f64fe..7325941ed0 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1,18 +1,23 @@ -#include "slic3r/GUI/3DScene.hpp" +///|/ Copyright (c) Prusa Research 2016 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, David Kocík @kocikdav, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2017 Eyal Soha @eyal0 +///|/ Copyright (c) Slic3r 2015 Alessandro Ranellucci @alranel +///|/ +///|/ ported from lib/Slic3r/GUI/3DScene.pm: +///|/ Copyright (c) Prusa Research 2016 - 2019 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka +///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2013 Guillaume Seguin @iXce +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include -#if ENABLE_SMOOTH_NORMALS -#include -#include -#include -#endif // ENABLE_SMOOTH_NORMALS - #include "3DScene.hpp" #include "GLShader.hpp" #include "GUI_App.hpp" #include "GUI_Colors.hpp" #include "Plater.hpp" #include "BitmapCache.hpp" +#include "Camera.hpp" #include "libslic3r/BuildVolume.hpp" #include "libslic3r/ExtrusionEntity.hpp" @@ -70,262 +75,32 @@ void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char #endif // HAS_GLSAFE // BBS -std::vector> get_extruders_colors() +std::vector get_extruders_colors() { - unsigned char rgba_color[4] = {}; - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - std::vector> colors_out(colors.size()); + Slic3r::ColorRGBA rgba_color; + std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + std::vector colors_out(colors.size()); for (const std::string &color : colors) { - Slic3r::GUI::BitmapCache::parse_color4(color, rgba_color); + Slic3r::decode_color(color, rgba_color); size_t color_idx = &color - &colors.front(); - colors_out[color_idx] = { - float(rgba_color[0]) / 255.f, - float(rgba_color[1]) / 255.f, - float(rgba_color[2]) / 255.f, - float(rgba_color[3]) / 255.f, - }; + colors_out[color_idx] = rgba_color; } return colors_out; } float FullyTransparentMaterialThreshold = 0.1f; float FullTransparentModdifiedToFixAlpha = 0.3f; -std::array adjust_color_for_rendering(const std::array &colors) + +Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors) { - if (colors[3] < FullyTransparentMaterialThreshold) { // completely transparent - std::array new_color; - new_color[0] = 1; - new_color[1] = 1; - new_color[2] = 1; - new_color[3] = FullTransparentModdifiedToFixAlpha; - return new_color; - } + if (colors.a() < FullyTransparentMaterialThreshold) { // completely transparent + return {1, 1, 1, FullTransparentModdifiedToFixAlpha}; + } return colors; } namespace Slic3r { -#if ENABLE_SMOOTH_NORMALS -static void smooth_normals_corner(TriangleMesh& mesh, std::vector& normals) -{ - using MapMatrixXfUnaligned = Eigen::Map>; - using MapMatrixXiUnaligned = Eigen::Map>; - - std::vector face_normals = its_face_normals(mesh.its); - - Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), - Eigen::Index(mesh.its.vertices.size()), 3).cast(); - Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(), - Eigen::Index(mesh.its.indices.size()), 3); - Eigen::MatrixXd in_normals = MapMatrixXfUnaligned(face_normals.front().data(), - Eigen::Index(face_normals.size()), 3).cast(); - Eigen::MatrixXd out_normals; - - igl::per_corner_normals(vertices, indices, in_normals, 1.0, out_normals); - - normals = std::vector(mesh.its.vertices.size()); - for (size_t i = 0; i < mesh.its.indices.size(); ++i) { - for (size_t j = 0; j < 3; ++j) { - normals[mesh.its.indices[i][j]] = out_normals.row(i * 3 + j).cast(); - } - } -} - -static void smooth_normals_vertex(TriangleMesh& mesh, std::vector& normals) -{ - using MapMatrixXfUnaligned = Eigen::Map>; - using MapMatrixXiUnaligned = Eigen::Map>; - - Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), - Eigen::Index(mesh.its.vertices.size()), 3).cast(); - Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(), - Eigen::Index(mesh.its.indices.size()), 3); - Eigen::MatrixXd out_normals; - -// igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM, out_normals); -// igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA, out_normals); - igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE, out_normals); -// igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT, out_normals); - - normals = std::vector(mesh.its.vertices.size()); - for (size_t i = 0; i < static_cast(out_normals.rows()); ++i) { - normals[i] = out_normals.row(i).cast(); - } -} -#endif // ENABLE_SMOOTH_NORMALS - -#if ENABLE_SMOOTH_NORMALS -void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals) -#else -void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh& mesh) -#endif // ENABLE_SMOOTH_NORMALS -{ - assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); - assert(quad_indices.empty() && triangle_indices_size == 0); - assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size()); - -#if ENABLE_SMOOTH_NORMALS - if (smooth_normals) { - TriangleMesh new_mesh(mesh); - std::vector normals; - smooth_normals_corner(new_mesh, normals); -// smooth_normals_vertex(new_mesh, normals); - - this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 2 * new_mesh.its.vertices.size()); - for (size_t i = 0; i < new_mesh.its.vertices.size(); ++i) { - const stl_vertex& v = new_mesh.its.vertices[i]; - const stl_normal& n = normals[i]; - this->push_geometry(v(0), v(1), v(2), n(0), n(1), n(2)); - } - - for (size_t i = 0; i < new_mesh.its.indices.size(); ++i) { - const stl_triangle_vertex_indices& idx = new_mesh.its.indices[i]; - this->push_triangle(idx(0), idx(1), idx(2)); - } - } - else { -#endif // ENABLE_SMOOTH_NORMALS - this->load_its_flat_shading(mesh.its); -#if ENABLE_SMOOTH_NORMALS - } -#endif // ENABLE_SMOOTH_NORMALS -} - -void GLIndexedVertexArray::load_its_flat_shading(const indexed_triangle_set &its) -{ - this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * its.indices.size()); - unsigned int vertices_count = 0; - for (int i = 0; i < int(its.indices.size()); ++ i) { - stl_triangle_vertex_indices face = its.indices[i]; - stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; - stl_vertex n = face_normal_normalized(vertex); - for (int j = 0; j < 3; ++j) - this->push_geometry(vertex[j](0), vertex[j](1), vertex[j](2), n(0), n(1), n(2)); - this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2); - vertices_count += 3; - } - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1%, indices size %2%, vertices %3%, triangles %4% ") - %this %its.indices.size() %this->vertices_and_normals_interleaved.size() %this->triangle_indices.size() ; -} - -void GLIndexedVertexArray::finalize_geometry(bool opengl_initialized) -{ - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - assert(this->triangle_indices_VBO_id == 0); - assert(this->quad_indices_VBO_id == 0); - - if (! opengl_initialized) { - // Shrink the data vectors to conserve memory in case the data cannot be transfered to the OpenGL driver yet. - this->shrink_to_fit(); - return; - } - - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1% ") %this; - if (! this->vertices_and_normals_interleaved.empty()) { - glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->vertices_and_normals_interleaved.clear(); - } - if (! this->triangle_indices.empty()) { - glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - this->triangle_indices.clear(); - } - if (! this->quad_indices.empty()) { - glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - this->quad_indices.clear(); - } -} - -void GLIndexedVertexArray::release_geometry() -{ - if (this->vertices_and_normals_interleaved_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); - this->vertices_and_normals_interleaved_VBO_id = 0; - } - if (this->triangle_indices_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id)); - this->triangle_indices_VBO_id = 0; - } - if (this->quad_indices_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id)); - this->quad_indices_VBO_id = 0; - } - this->clear(); -} - -void GLIndexedVertexArray::render() const -{ - assert(this->vertices_and_normals_interleaved_VBO_id != 0); - assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - // Render using the Vertex Buffer Objects. - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr)); - glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr)); - glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -} - -void GLIndexedVertexArray::render( - const std::pair& tverts_range, - const std::pair& qverts_range) const -{ - // this method has been called before calling finalize() ? - if (this->vertices_and_normals_interleaved_VBO_id == 0 && !this->vertices_and_normals_interleaved.empty()) - return; - - assert(this->vertices_and_normals_interleaved_VBO_id != 0); - assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); - - // Render using the Vertex Buffer Objects. - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -} const float GLVolume::SinkingContours::HalfWidth = 0.25f; @@ -333,18 +108,22 @@ void GLVolume::SinkingContours::render() { update(); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_shift.x(), m_shift.y(), m_shift.z())); + GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); + if (shader == nullptr) + return; + + const GUI::Camera& camera = GUI::wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::assemble_transform(m_shift)); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); m_model.render(); - glsafe(::glPopMatrix()); } void GLVolume::SinkingContours::update() { - int object_idx = m_parent.object_idx(); - Model& model = GUI::wxGetApp().plater()->model(); + const int object_idx = m_parent.object_idx(); + const Model& model = GUI::wxGetApp().plater()->model(); - if (0 <= object_idx && object_idx < (int)model.objects.size() && m_parent.is_sinking() && !m_parent.is_below_printbed()) { + if (0 <= object_idx && object_idx < int(model.objects.size()) && m_parent.is_sinking() && !m_parent.is_below_printbed()) { const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box(); if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) { m_old_box = box; @@ -353,28 +132,25 @@ void GLVolume::SinkingContours::update() const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh(); m_model.reset(); - GUI::GLModel::InitializationData init_data; + GUI::GLModel::Geometry init_data; + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + unsigned int vertices_counter = 0; MeshSlicingParams slicing_params; slicing_params.trafo = m_parent.world_matrix(); - Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); - for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Triangles; + const Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); + for (const ExPolygon& expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { const std::vector triangulation = triangulate_expolygon_3d(expoly); + init_data.reserve_vertices(init_data.vertices_count() + triangulation.size()); + init_data.reserve_indices(init_data.indices_count() + triangulation.size()); for (const Vec3d& v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.015f)); // add a small positive z to avoid z-fighting - entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t positions_count = entity.positions.size(); - if (positions_count % 3 == 0) { - entity.indices.emplace_back(positions_count - 3); - entity.indices.emplace_back(positions_count - 2); - entity.indices.emplace_back(positions_count - 1); - } + init_data.add_vertex((Vec3f)(v.cast() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } - init_data.entities.emplace_back(entity); } - - m_model.init_from(init_data); + m_model.init_from(std::move(init_data)); } else m_shift = box.center() - m_old_box.center(); @@ -383,21 +159,21 @@ void GLVolume::SinkingContours::update() m_model.reset(); } -std::array GLVolume::DISABLED_COLOR = { 0.25f, 0.25f, 0.25f, 1.0f }; -std::array GLVolume::SLA_SUPPORT_COLOR = { 0.75f, 0.75f, 0.75f, 1.0f }; -std::array GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f }; +ColorRGBA GLVolume::DISABLED_COLOR = ColorRGBA::DARK_GRAY(); +ColorRGBA GLVolume::SLA_SUPPORT_COLOR = ColorRGBA::LIGHT_GRAY(); +ColorRGBA GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f }; // BBS -std::array GLVolume::NEUTRAL_COLOR = { 0.8f, 0.8f, 0.8f, 1.0f }; -std::array GLVolume::UNPRINTABLE_COLOR = { 0.0f, 0.0f, 0.0f, 0.5f }; +ColorRGBA GLVolume::NEUTRAL_COLOR = { 0.8f, 0.8f, 0.8f, 1.0f }; +ColorRGBA GLVolume::UNPRINTABLE_COLOR = { 0.0f, 0.0f, 0.0f, 0.5f }; -std::array GLVolume::MODEL_MIDIFIER_COL = {1.0f, 1.0f, 0.0f, 0.6f}; -std::array GLVolume::MODEL_NEGTIVE_COL = {0.3f, 0.3f, 0.3f, 0.4f}; -std::array GLVolume::SUPPORT_ENFORCER_COL = {0.3f, 0.3f, 1.0f, 0.4f}; -std::array GLVolume::SUPPORT_BLOCKER_COL = {1.0f, 0.3f, 0.3f, 0.4f}; +ColorRGBA GLVolume::MODEL_MIDIFIER_COL = {1.0f, 1.0f, 0.0f, 0.6f}; +ColorRGBA GLVolume::MODEL_NEGTIVE_COL = {0.3f, 0.3f, 0.3f, 0.4f}; +ColorRGBA GLVolume::SUPPORT_ENFORCER_COL = {0.3f, 0.3f, 1.0f, 0.4f}; +ColorRGBA GLVolume::SUPPORT_BLOCKER_COL = {1.0f, 0.3f, 0.3f, 0.4f}; -std::array GLVolume::MODEL_HIDDEN_COL = {0.f, 0.f, 0.f, 0.3f}; +ColorRGBA GLVolume::MODEL_HIDDEN_COL = {0.f, 0.f, 0.f, 0.3f}; -std::array, 5> GLVolume::MODEL_COLOR = { { +std::array GLVolume::MODEL_COLOR = { { { 1.0f, 1.0f, 0.0f, 1.f }, { 1.0f, 0.5f, 0.5f, 1.f }, { 0.5f, 1.0f, 0.5f, 1.f }, @@ -407,25 +183,25 @@ std::array, 5> GLVolume::MODEL_COLOR = { { void GLVolume::update_render_colors() { - GLVolume::DISABLED_COLOR = GLColor(RenderColor::colors[RenderCol_Model_Disable]); - GLVolume::NEUTRAL_COLOR = GLColor(RenderColor::colors[RenderCol_Model_Neutral]); - GLVolume::MODEL_COLOR[0] = GLColor(RenderColor::colors[RenderCol_Modifier]); - GLVolume::MODEL_COLOR[1] = GLColor(RenderColor::colors[RenderCol_Negtive_Volume]); - GLVolume::MODEL_COLOR[2] = GLColor(RenderColor::colors[RenderCol_Support_Enforcer]); - GLVolume::MODEL_COLOR[3] = GLColor(RenderColor::colors[RenderCol_Support_Blocker]); - GLVolume::UNPRINTABLE_COLOR = GLColor(RenderColor::colors[RenderCol_Model_Unprintable]); + GLVolume::DISABLED_COLOR = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Model_Disable]); + GLVolume::NEUTRAL_COLOR = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Model_Neutral]); + GLVolume::MODEL_COLOR[0] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Modifier]); + GLVolume::MODEL_COLOR[1] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Negtive_Volume]); + GLVolume::MODEL_COLOR[2] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Support_Enforcer]); + GLVolume::MODEL_COLOR[3] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Support_Blocker]); + GLVolume::UNPRINTABLE_COLOR = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Model_Unprintable]); } void GLVolume::load_render_colors() { - RenderColor::colors[RenderCol_Model_Disable] = IMColor(GLVolume::DISABLED_COLOR); - RenderColor::colors[RenderCol_Model_Neutral] = IMColor(GLVolume::NEUTRAL_COLOR); - RenderColor::colors[RenderCol_Modifier] = IMColor(GLVolume::MODEL_COLOR[0]); - RenderColor::colors[RenderCol_Negtive_Volume] = IMColor(GLVolume::MODEL_COLOR[1]); - RenderColor::colors[RenderCol_Support_Enforcer] = IMColor(GLVolume::MODEL_COLOR[2]); - RenderColor::colors[RenderCol_Support_Blocker] = IMColor(GLVolume::MODEL_COLOR[3]); - RenderColor::colors[RenderCol_Model_Unprintable]= IMColor(GLVolume::UNPRINTABLE_COLOR); + RenderColor::colors[RenderCol_Model_Disable] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::DISABLED_COLOR); + RenderColor::colors[RenderCol_Model_Neutral] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::NEUTRAL_COLOR); + RenderColor::colors[RenderCol_Modifier] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[0]); + RenderColor::colors[RenderCol_Negtive_Volume] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[1]); + RenderColor::colors[RenderCol_Support_Enforcer] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[2]); + RenderColor::colors[RenderCol_Support_Blocker] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[3]); + RenderColor::colors[RenderCol_Model_Unprintable] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::UNPRINTABLE_COLOR); } GLVolume::GLVolume(float r, float g, float b, float a) @@ -451,33 +227,19 @@ GLVolume::GLVolume(float r, float g, float b, float a) , force_native_color(false) , force_neutral_color(false) , force_sinking_contours(false) + , picking(false) , tverts_range(0, size_t(-1)) - , qverts_range(0, size_t(-1)) { color = { r, g, b, a }; set_render_color(color); mmuseg_ts = 0; } -void GLVolume::set_color(const std::array& rgba) -{ - color = rgba; -} // BBS float GLVolume::explosion_ratio = 1.0; float GLVolume::last_explosion_ratio = 1.0; -void GLVolume::set_render_color(float r, float g, float b, float a) -{ - render_color = { r, g, b, a }; -} - -void GLVolume::set_render_color(const std::array& rgba) -{ - render_color = rgba; -} - void GLVolume::set_render_color() { bool outside = is_outside || is_below_printbed(); @@ -514,57 +276,45 @@ void GLVolume::set_render_color() #endif else { //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(color); + ColorRGBA new_color = adjust_color_for_rendering(color); set_render_color(new_color); } } if (force_transparent) { - if (color[3] < FullyTransparentMaterialThreshold) { - render_color[3] = FullTransparentModdifiedToFixAlpha; + if (color.a() < FullyTransparentMaterialThreshold) { + render_color.a(FullTransparentModdifiedToFixAlpha); } else { - render_color[3] = color[3]; + render_color.a(color.a()); } } //BBS set unprintable color if (!printable) { - render_color[0] = UNPRINTABLE_COLOR[0]; - render_color[1] = UNPRINTABLE_COLOR[1]; - render_color[2] = UNPRINTABLE_COLOR[2]; - render_color[3] = UNPRINTABLE_COLOR[3]; + render_color = UNPRINTABLE_COLOR; } //BBS set invisible color if (!visible) { - render_color[0] = MODEL_HIDDEN_COL[0]; - render_color[1] = MODEL_HIDDEN_COL[1]; - render_color[2] = MODEL_HIDDEN_COL[2]; - render_color[3] = MODEL_HIDDEN_COL[3]; + render_color = MODEL_HIDDEN_COL; } } -std::array color_from_model_volume(const ModelVolume& model_volume) +ColorRGBA color_from_model_volume(const ModelVolume& model_volume) { - std::array color = {0.0f, 0.0f, 0.0f, 1.0f}; - if (model_volume.is_negative_volume()) { + ColorRGBA color; + if (model_volume.is_negative_volume()) return GLVolume::MODEL_NEGTIVE_COL; - } - else if (model_volume.is_modifier()) { + else if (model_volume.is_modifier()) #if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT return GLVolume::MODEL_MIDIFIER_COL; #else - color[0] = 0.2f; - color[1] = 1.0f; - color[2] = 0.2f; + color = { 0.2f, 1.0f, 0.2f, 1.0f }; #endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT - } - else if (model_volume.is_support_blocker()) { + else if (model_volume.is_support_blocker()) return GLVolume::SUPPORT_BLOCKER_COL; - } - else if (model_volume.is_support_enforcer()) { + else if (model_volume.is_support_enforcer()) return GLVolume::SUPPORT_ENFORCER_COL; - } return color; } @@ -631,281 +381,100 @@ const BoundingBoxf3& GLVolume::transformed_non_sinking_bounding_box() const void GLVolume::set_range(double min_z, double max_z) { - this->qverts_range.first = 0; - this->qverts_range.second = this->indexed_vertex_array.quad_indices_size; this->tverts_range.first = 0; - this->tverts_range.second = this->indexed_vertex_array.triangle_indices_size; - if (! this->print_zs.empty()) { + this->tverts_range.second = this->model.indices_count(); + + if (!this->print_zs.empty()) { // The Z layer range is specified. // First test whether the Z span of this object is not out of (min_z, max_z) completely. - if (this->print_zs.front() > max_z || this->print_zs.back() < min_z) { - this->qverts_range.second = 0; + if (this->print_zs.front() > max_z || this->print_zs.back() < min_z) this->tverts_range.second = 0; - } else { + else { // Then find the lowest layer to be displayed. size_t i = 0; - for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++ i); - if (i == this->print_zs.size()) { + for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++i); + if (i == this->print_zs.size()) // This shall not happen. - this->qverts_range.second = 0; this->tverts_range.second = 0; - } else { + else { // Remember start of the layer. - this->qverts_range.first = this->offsets[i * 2]; - this->tverts_range.first = this->offsets[i * 2 + 1]; + this->tverts_range.first = this->offsets[i]; // Some layers are above $min_z. Which? - for (; i < this->print_zs.size() && this->print_zs[i] <= max_z; ++ i); - if (i < this->print_zs.size()) { - this->qverts_range.second = this->offsets[i * 2]; - this->tverts_range.second = this->offsets[i * 2 + 1]; - } + for (; i < this->print_zs.size() && this->print_zs[i] <= max_z; ++i); + if (i < this->print_zs.size()) + this->tverts_range.second = this->offsets[i]; } } } } -//BBS: add outline related logic -//static unsigned char stencil_data[1284][2944]; -void GLVolume::render(bool with_outline) const +void GLVolume::render() { if (!is_active) return; - if (this->is_left_handed()) - glFrontFace(GL_CW); - glsafe(::glCullFace(GL_BACK)); - glsafe(::glPushMatrix()); + GLShaderProgram *shader = GUI::wxGetApp().get_current_shader(); + if (shader == nullptr) + return; - // BBS: add logic for mmu segmentation rendering - auto render_body = [&]() { - bool color_volume = false; - ModelObjectPtrs& model_objects = GUI::wxGetApp().model().objects; - do { - if ((!printable) || object_idx() >= model_objects.size()) - break; + ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects; + std::vector colors = get_extruders_colors(); - ModelObject* mo = model_objects[object_idx()]; - if (volume_idx() >= mo->volumes.size()) - break; + simple_render(shader, model_objects, colors); +} - ModelVolume* mv = mo->volumes[volume_idx()]; - if (mv->mmu_segmentation_facets.empty()) - break; +//BBS: add outline related logic +void GLVolume::render_with_outline(const Transform3d &view_model_matrix) +{ + if (!is_active) + return; - color_volume = true; - if (mv->mmu_segmentation_facets.timestamp() != mmuseg_ts) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1%, name %2%, current mmuseg_ts %3%, current color size %4%") - %this %this->name %mmuseg_ts %mmuseg_ivas.size() ; - mmuseg_ivas.clear(); - std::vector its_per_color; - mv->mmu_segmentation_facets.get_facets(*mv, its_per_color); - mmuseg_ivas.resize(its_per_color.size()); - for (int idx = 0; idx < its_per_color.size(); idx++) { - mmuseg_ivas[idx].load_its_flat_shading(its_per_color[idx]); - mmuseg_ivas[idx].finalize_geometry(true); - } + GLShaderProgram *shader = GUI::wxGetApp().get_current_shader(); + if (shader == nullptr) + return; - mmuseg_ts = mv->mmu_segmentation_facets.timestamp(); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1%, name %2%, new mmuseg_ts %3%, new color size %4%") - %this %this->name %mmuseg_ts %mmuseg_ivas.size(); - } - } while (0); + ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects; + std::vector colors = get_extruders_colors(); - if (color_volume) { - GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); - std::vector> colors = get_extruders_colors(); + glEnable(GL_STENCIL_TEST); + glStencilMask(0xFF); + glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE); + glClear(GL_STENCIL_BUFFER_BIT); + glStencilFunc(GL_ALWAYS, 0xff, 0xFF); - //when force_transparent, we need to keep the alpha - if (force_native_color && (render_color[3] < 1.0)) { - for (int index = 0; index < colors.size(); index ++) - colors[index][3] = render_color[3]; - } - glsafe(::glMultMatrixd(world_matrix().data())); - for (int idx = 0; idx < mmuseg_ivas.size(); idx++) { - GLIndexedVertexArray& iva = mmuseg_ivas[idx]; - if (iva.triangle_indices_size == 0 && iva.quad_indices_size == 0) - continue; + simple_render(shader, model_objects, colors); - if (shader) { - if (idx == 0) { - ModelObject* mo = model_objects[object_idx()]; - ModelVolume* mv = mo->volumes[volume_idx()]; - int extruder_id = mv->extruder_id(); - //shader->set_uniform("uniform_color", colors[extruder_id - 1]); - //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(colors[extruder_id - 1]); - shader->set_uniform("uniform_color", new_color); - } - else { - if (idx <= colors.size()) { - //shader->set_uniform("uniform_color", colors[idx - 1]); - //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(colors[idx - 1]); - shader->set_uniform("uniform_color", new_color); - } - else { - //shader->set_uniform("uniform_color", colors[0]); - //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(colors[0]); - shader->set_uniform("uniform_color", new_color); - } - } - } - iva.render(this->tverts_range, this->qverts_range); - /*if (force_native_color && (render_color[3] < 1.0)) { - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1%, name %2%, tverts_range {%3,%4}, qverts_range{%5%, %6%}") - %this %this->name %this->tverts_range.first %this->tverts_range.second - % this->qverts_range.first % this->qverts_range.second; - }*/ - } - } - else { - glsafe(::glMultMatrixd(world_matrix().data())); - this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); - } - }; + // 2nd. render pass: now draw slightly scaled versions of the objects, this time disabling stencil writing. + // Because the stencil buffer is now filled with several 1s. The parts of the buffer that are 1 are not drawn, thus only drawing + // the objects' size differences, making it look like borders. + glStencilFunc(GL_NOTEQUAL, 0xff, 0xFF); + glStencilMask(0x00); + float scale = 1.02f; + ColorRGBA body_color = { 1.0f, 1.0f, 1.0f, 1.0f }; //red - //BBS: add logic of outline rendering - GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); - //BOOST_LOG_TRIVIAL(info) << boost::format(": %1%, with_outline %2%, shader %3%.")%__LINE__ %with_outline %shader; - if (with_outline && shader != nullptr) - { - do - { - glEnable(GL_STENCIL_TEST); - glStencilMask(0xFF); - glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE); - glClear(GL_STENCIL_BUFFER_BIT); - glStencilFunc(GL_ALWAYS, 0xff, 0xFF); - //another way use depth buffer - //glsafe(::glEnable(GL_DEPTH_TEST)); - //glsafe(::glDepthFunc(GL_ALWAYS)); - //glsafe(::glDepthMask(GL_FALSE)); - //glsafe(::glEnable(GL_BLEND)); - //glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + model.set_color(body_color); + shader->set_uniform("is_outline", true); - /*GLShaderProgram* outline_shader = GUI::wxGetApp().get_shader("outline"); - if (outline_shader == nullptr) - { - glDisable(GL_STENCIL_TEST); - this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); - break; - } - shader->stop_using(); - outline_shader->start_using(); - //float scale_ratio = 1.02f; - std::array outline_color = { 0.0f, 1.0f, 0.0f, 1.0f }; + Transform3d matrix = view_model_matrix; + matrix.scale(scale); + shader->set_uniform("view_model_matrix", matrix); + if (tverts_range == std::make_pair(0, -1)) + model.render(); + else + model.render(this->tverts_range); - outline_shader->set_uniform("uniform_color", outline_color);*/ -#if 0 //dump stencil buffer - int i = 100, j = 100; - std::string file_name; - FILE* file = NULL; - memset(stencil_data, 0, sizeof(stencil_data)); - glReadPixels(0, 0, 2936, 1083, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencil_data); - for (i = 100; i < 1083; i++) - { - for (j = 100; j < 2936; j++) - { - if (stencil_data[i][j] != 0) - { - file_name = "before_stencil_index_" + std::to_string(i) + "x" + std::to_string(j) + ".a8"; - break; - } - } + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("is_outline", false); - if (stencil_data[i][j] != 0) - break; - } - file = fopen(file_name.c_str(), "w"); - if (file) - { - fwrite(stencil_data, 2936 * 1083, 1, file); - fclose(file); - } -#endif - render_body(); - //BOOST_LOG_TRIVIAL(info) << boost::format(": %1%, outline render body, shader name %2%")%__LINE__ %shader->get_name(); - -#if 0 //dump stencil buffer after first rendering - memset(stencil_data, 0, sizeof(stencil_data)); - glReadPixels(0, 0, 2936, 1083, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencil_data); - for (i = 100; i < 1083; i++) - { - for (j = 100; j < 2936; j++) - if (stencil_data[i][j] != 0) - { - file_name = "after_stencil_index_" + std::to_string(i) + "x" + std::to_string(j) + ".a8"; - break; - } - - if (stencil_data[i][j] != 0) - break; - } - - file = fopen(file_name.c_str(), "w"); - if (file) - { - fwrite(stencil_data, 2936 * 1083, 1, file); - fclose(file); - } -#endif - // 2nd. render pass: now draw slightly scaled versions of the objects, this time disabling stencil writing. - // Because the stencil buffer is now filled with several 1s. The parts of the buffer that are 1 are not drawn, thus only drawing - // the objects' size differences, making it look like borders. - // ----------------------------------------------------------------------------------------------------------------------------- - /*GLShaderProgram* outline_shader = GUI::wxGetApp().get_shader("outline"); - if (outline_shader == nullptr) - { - glDisable(GL_STENCIL_TEST); - break; - } - shader->stop_using(); - outline_shader->start_using();*/ - //outline_shader->stop_using(); - //shader->start_using(); - - glStencilFunc(GL_NOTEQUAL, 0xff, 0xFF); - glStencilMask(0x00); - float scale = 1.02f; - std::array body_color = { 1.0f, 1.0f, 1.0f, 1.0f }; //red - - shader->set_uniform("uniform_color", body_color); - shader->set_uniform("is_outline", true); - glsafe(::glPopMatrix()); - glsafe(::glPushMatrix()); - - Transform3d matrix = world_matrix(); - matrix.scale(scale); - glsafe(::glMultMatrixd(matrix.data())); - this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); - //BOOST_LOG_TRIVIAL(info) << boost::format(": %1%, outline render for body, shader name %2%")%__LINE__ %shader->get_name(); - shader->set_uniform("is_outline", false); - - //glStencilMask(0xFF); - //glStencilFunc(GL_ALWAYS, 0, 0xFF); - glDisable(GL_STENCIL_TEST); - //glEnable(GL_DEPTH_TEST); - //outline_shader->stop_using(); - //shader->start_using(); - } while (0); - } - else { - render_body(); - //BOOST_LOG_TRIVIAL(info) << boost::format(": %1%, normal render.")%__LINE__; - } - glsafe(::glPopMatrix()); - if (this->is_left_handed()) - glFrontFace(GL_CCW); + glDisable(GL_STENCIL_TEST); } //BBS add render for simple case -void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector>& extruder_colors) const +void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector extruder_colors) { if (this->is_left_handed()) glFrontFace(GL_CW); glsafe(::glCullFace(GL_BACK)); - glsafe(::glPushMatrix()); bool color_volume = false; ModelObject* model_object = nullptr; @@ -923,57 +492,59 @@ void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_obj color_volume = true; if (model_volume->mmu_segmentation_facets.timestamp() != mmuseg_ts) { - mmuseg_ivas.clear(); + mmuseg_models.clear(); std::vector its_per_color; model_volume->mmu_segmentation_facets.get_facets(*model_volume, its_per_color); - mmuseg_ivas.resize(its_per_color.size()); + mmuseg_models.resize(its_per_color.size()); for (int idx = 0; idx < its_per_color.size(); idx++) { - mmuseg_ivas[idx].load_its_flat_shading(its_per_color[idx]); - mmuseg_ivas[idx].finalize_geometry(true); + mmuseg_models[idx].init_from(its_per_color[idx]); } mmuseg_ts = model_volume->mmu_segmentation_facets.timestamp(); } } while (0); - if (color_volume) { - glsafe(::glMultMatrixd(world_matrix().data())); - for (int idx = 0; idx < mmuseg_ivas.size(); idx++) { - GLIndexedVertexArray& iva = mmuseg_ivas[idx]; - if (iva.triangle_indices_size == 0 && iva.quad_indices_size == 0) + if (color_volume && !picking) { + // when force_transparent, we need to keep the alpha + if (force_native_color && render_color.is_transparent()) { + for (auto &extruder_color : extruder_colors) + extruder_color.a(render_color.a()); + } + + for (int idx = 0; idx < mmuseg_models.size(); idx++) { + GUI::GLModel &m = mmuseg_models[idx]; + if (!m.is_initialized()) continue; - if (shader) { - if (idx == 0) { - int extruder_id = model_volume->extruder_id(); + if (idx == 0) { + int extruder_id = model_volume->extruder_id(); + //to make black not too hard too see + ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[extruder_id - 1]); + m.set_color(new_color); + } + else { + if (idx <= extruder_colors.size()) { //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(extruder_colors[extruder_id - 1]); - shader->set_uniform("uniform_color", new_color); + ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[idx - 1]); + m.set_color(new_color); } else { - if (idx <= extruder_colors.size()) { - //shader->set_uniform("uniform_color", extruder_colors[idx - 1]); - //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(extruder_colors[idx - 1]); - shader->set_uniform("uniform_color", new_color); - } - else { - //shader->set_uniform("uniform_color", extruder_colors[0]); - //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(extruder_colors[0]); - shader->set_uniform("uniform_color", new_color); - } + //to make black not too hard too see + ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[0]); + m.set_color(new_color); } } - iva.render(this->tverts_range, this->qverts_range); + if (tverts_range == std::make_pair(0, -1)) + m.render(); + else + m.render(this->tverts_range); } + } else { + if (tverts_range == std::make_pair(0, -1)) + model.render(); + else + model.render(this->tverts_range); } - else { - glsafe(::glMultMatrixd(world_matrix().data())); - this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); - } - - glsafe(::glPopMatrix()); if (this->is_left_handed()) glFrontFace(GL_CCW); } @@ -999,43 +570,41 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } -GLWipeTowerVolume::GLWipeTowerVolume(const std::vector>& colors) +GLWipeTowerVolume::GLWipeTowerVolume(const std::vector& colors) : GLVolume() { m_colors = colors; } -void GLWipeTowerVolume::render(bool with_outline) const +void GLWipeTowerVolume::render() { if (!is_active) return; - if (m_colors.size() == 0 || m_colors.size() != iva_per_colors.size()) + if (m_colors.size() == 0 || m_colors.size() != model_per_colors.size()) return; if (this->is_left_handed()) glFrontFace(GL_CW); glsafe(::glCullFace(GL_BACK)); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(world_matrix().data())); - GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); for (int i = 0; i < m_colors.size(); i++) { - if (shader) { - std::array new_color = adjust_color_for_rendering(m_colors[i]); - shader->set_uniform("uniform_color", new_color); + if (!picking) { + ColorRGBA new_color = adjust_color_for_rendering(m_colors[i]); + this->model_per_colors[i].set_color(new_color); + } else { + this->model_per_colors[i].set_color(model.get_color()); } - this->iva_per_colors[i].render(); + this->model_per_colors[i].render(); } - glsafe(::glPopMatrix()); if (this->is_left_handed()) glFrontFace(GL_CCW); } bool GLWipeTowerVolume::IsTransparent() { for (size_t i = 0; i < m_colors.size(); i++) { - if (m_colors[i][3] < 1.0f) { + if (m_colors[i].is_transparent()) { return true; } } @@ -1043,48 +612,42 @@ bool GLWipeTowerVolume::IsTransparent() { } std::vector GLVolumeCollection::load_object( - const ModelObject *model_object, - int obj_idx, - const std::vector &instance_idxs, - const std::string &color_by, - bool opengl_initialized) + const ModelObject* model_object, + int obj_idx, + const std::vector& instance_idxs) { std::vector volumes_idx; for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) - volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized)); + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx)); return volumes_idx; } int GLVolumeCollection::load_object_volume( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, - bool opengl_initialized, bool in_assemble_view, bool use_loaded_id) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); const ModelInstance *instance = model_object->instances[instance_idx]; - const TriangleMesh &mesh = model_volume->mesh(); - std::array color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4]; - color[3] = model_volume->is_model_part() ? 0.7f : 0.4f; - this->volumes.emplace_back(new GLVolume(color)); + std::shared_ptr mesh = model_volume->mesh_ptr(); + this->volumes.emplace_back(new GLVolume()); GLVolume& v = *this->volumes.back(); v.set_color(color_from_model_volume(*model_volume)); v.name = model_volume->name; + #if ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.load_mesh(mesh, true); + v.model.init_from(mesh, true); #else - v.indexed_vertex_array.load_mesh(mesh); + v.model.init_from(*mesh); + v.mesh_raycaster = std::make_unique(mesh); #endif // ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); - if (model_volume->is_model_part()) - { + if (model_volume->is_model_part()) { // GLVolume will reference a convex hull from model_volume! v.set_convex_hull(model_volume->get_convex_hull_shared_ptr()); if (extruder_id != -1) @@ -1112,14 +675,13 @@ int GLVolumeCollection::load_object_volume( // This function produces volumes for multiple instances in a single shot, // as some object specific mesh conversions may be expensive. void GLVolumeCollection::load_object_auxiliary( - const SLAPrintObject *print_object, + const SLAPrintObject* print_object, int obj_idx, // pairs of const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp, - bool opengl_initialized) + size_t timestamp) { assert(print_object->is_step_done(milestone)); Transform3d mesh_trafo_inv = print_object->trafo().inverse(); @@ -1133,11 +695,12 @@ void GLVolumeCollection::load_object_auxiliary( this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); #if ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.load_mesh(mesh, true); + v.model.init_from(mesh, true); #else - v.indexed_vertex_array.load_mesh(mesh); + v.model.init_from(mesh); + v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR); + v.mesh_raycaster = std::make_unique(std::make_shared(mesh)); #endif // ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. @@ -1155,7 +718,7 @@ void GLVolumeCollection::load_object_auxiliary( int GLVolumeCollection::load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, - float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized) + float rotation_angle, bool size_unknown, float brim_width) { int plate_idx = obj_idx - 1000; @@ -1164,8 +727,8 @@ int GLVolumeCollection::load_wipe_tower_preview( if (height == 0.0f) height = 0.1f; - std::vector> extruder_colors = get_extruders_colors(); - std::vector> colors; + std::vector extruder_colors = get_extruders_colors(); + std::vector colors; GUI::PartPlateList& ppl = GUI::wxGetApp().plater()->get_partplate_list(); std::vector plate_extruders = ppl.get_plate(plate_idx)->get_extruders(true); TriangleMesh wipe_tower_shell = make_cube(width, depth, height); @@ -1176,27 +739,19 @@ int GLVolumeCollection::load_wipe_tower_preview( colors.push_back(extruder_colors[0]); } -#if 0 - // We'll make another mesh to show the brim (fixed layer height): - TriangleMesh brim_mesh = make_cube(width + 2.f * brim_width, depth + 2.f * brim_width, 0.2f); - brim_mesh.translate(-brim_width, -brim_width, 0.f); - mesh.merge(brim_mesh); -#endif - // Orca: make it transparent for(auto& color : colors) - color[3] = 0.66f; + color.a(0.66f); volumes.emplace_back(new GLWipeTowerVolume(colors)); GLWipeTowerVolume& v = *dynamic_cast(volumes.back()); - v.iva_per_colors.resize(colors.size()); + v.model_per_colors.resize(colors.size()); for (int i = 0; i < colors.size(); i++) { TriangleMesh color_part = make_cube(width, depth / colors.size(), height); color_part.translate({ 0.f, depth * i / colors.size(), 0. }); - v.iva_per_colors[i].load_mesh(color_part); - v.iva_per_colors[i].finalize_geometry(opengl_initialized); + v.model_per_colors[i].init_from(color_part); } - v.indexed_vertex_array.load_mesh(wipe_tower_shell); - v.indexed_vertex_array.finalize_geometry(opengl_initialized); + v.model.init_from(wipe_tower_shell); + v.mesh_raycaster = std::make_unique(std::make_shared(wipe_tower_shell)); v.set_convex_hull(wipe_tower_shell); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); @@ -1208,21 +763,19 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(volumes.size() - 1); } -GLVolume* GLVolumeCollection::new_toolpath_volume(const std::array& rgba, size_t reserve_vbo_floats) +GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba) { - GLVolume *out = new_nontoolpath_volume(rgba, reserve_vbo_floats); - out->is_extrusion_path = true; - return out; + GLVolume* out = new_nontoolpath_volume(rgba); + out->is_extrusion_path = true; + return out; } -GLVolume* GLVolumeCollection::new_nontoolpath_volume(const std::array& rgba, size_t reserve_vbo_floats) +GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba) { - GLVolume *out = new GLVolume(rgba); - out->is_extrusion_path = false; - // Reserving number of vertices (3x position + 3x color) - out->indexed_vertex_array.reserve(reserve_vbo_floats / 6); - this->volumes.emplace_back(out); - return out; + GLVolume* out = new GLVolume(rgba); + out->is_extrusion_path = false; + this->volumes.emplace_back(out); + return out; } GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) @@ -1232,7 +785,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i) { GLVolume* volume = volumes[i]; - bool is_transparent = (volume->render_color[3] < 1.0f); + bool is_transparent = volume->render_color.is_transparent(); auto tempGlwipeTowerVolume = dynamic_cast(volume); if (tempGlwipeTowerVolume) { is_transparent = tempGlwipeTowerVolume->IsTransparent(); @@ -1271,8 +824,8 @@ int GLVolumeCollection::get_selection_support_threshold_angle(bool &enable_suppo } //BBS: add outline drawing logic -void GLVolumeCollection::render( - GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d &view_matrix, std::function filter_func, bool with_outline) const +void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix, + std::function filter_func, bool with_outline) const { GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func); if (to_render.empty()) @@ -1282,6 +835,9 @@ void GLVolumeCollection::render( if (shader == nullptr) return; + GLShaderProgram* sink_shader = GUI::wxGetApp().get_shader("flat"); + GLShaderProgram* edges_shader = GUI::wxGetApp().get_shader("flat"); + if (type == ERenderType::Transparent) { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -1307,20 +863,27 @@ void GLVolumeCollection::render( #endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT // render sinking contours of non-hovered volumes - if (m_show_sinking_contours) - if (volume.first->is_sinking() && !volume.first->is_below_printbed() && - volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) { - shader->stop_using(); - volume.first->render_sinking_contours(); - shader->start_using(); + shader->stop_using(); + if (sink_shader != nullptr) { + sink_shader->start_using(); + if (m_show_sinking_contours) { + if (volume.first->is_sinking() && !volume.first->is_below_printbed() && + volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) { + volume.first->render_sinking_contours(); + } } + sink_shader->stop_using(); + } + shader->start_using(); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - shader->set_uniform("uniform_color", volume.first->render_color); - shader->set_uniform("z_range", m_z_range, 2); - shader->set_uniform("clipping_plane", m_clipping_plane, 4); + if (!volume.first->model.is_initialized()) + shader->set_uniform("uniform_color", volume.first->render_color); + shader->set_uniform("z_range", m_z_range); + shader->set_uniform("clipping_plane", m_clipping_plane); + shader->set_uniform("use_color_clip_plane", m_use_color_clip_plane); + shader->set_uniform("color_clip_plane", m_color_clip_plane); + shader->set_uniform("uniform_color_clip_plane_1", m_color_clip_plane_colors[0]); + shader->set_uniform("uniform_color_clip_plane_2", m_color_clip_plane_colors[1]); //BOOST_LOG_TRIVIAL(info) << boost::format("set uniform_color to {%1%, %2%, %3%, %4%}, with_outline=%5%, selected %6%") // %volume.first->render_color[0]%volume.first->render_color[1]%volume.first->render_color[2]%volume.first->render_color[3] // %with_outline%volume.first->selected; @@ -1360,8 +923,17 @@ void GLVolumeCollection::render( #endif // ENABLE_ENVIRONMENT_MAP glcheck(); - //BBS: add outline related logic - volume.first->render(with_outline && volume.first->selected); + volume.first->model.set_color(volume.first->render_color); + const Transform3d model_matrix = volume.first->world_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + //BBS: add outline related logic + if (with_outline && volume.first->selected) + volume.first->render_with_outline(view_matrix * model_matrix); + else + volume.first->render(); #if ENABLE_ENVIRONMENT_MAP if (use_environment_texture) @@ -1370,23 +942,24 @@ void GLVolumeCollection::render( glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); } if (m_show_sinking_contours) { - for (GLVolumeWithIdAndZ& volume : to_render) { - // render sinking contours of hovered/displaced volumes - if (volume.first->is_sinking() && !volume.first->is_below_printbed() && - (volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) { - shader->stop_using(); - glsafe(::glDepthFunc(GL_ALWAYS)); - volume.first->render_sinking_contours(); - glsafe(::glDepthFunc(GL_LESS)); - shader->start_using(); + shader->stop_using(); + if (sink_shader != nullptr) { + sink_shader->start_using(); + for (GLVolumeWithIdAndZ& volume : to_render) { + // render sinking contours of hovered/displaced volumes + if (volume.first->is_sinking() && !volume.first->is_below_printbed() && + (volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) { + glsafe(::glDepthFunc(GL_ALWAYS)); + volume.first->render_sinking_contours(); + glsafe(::glDepthFunc(GL_LESS)); + } } + sink_shader->start_using(); } + shader->start_using(); } if (disable_cullface) @@ -1539,83 +1112,53 @@ void GLVolumeCollection::reset_outside_state() void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig *config, bool is_update_alpha) { - static const float inv_255 = 1.0f / 255.0f; + + using ColorItem = std::pair; + std::vector colors; - struct Color - { - std::string text; - unsigned char rgba[4]; - - Color() - : text("") - { - rgba[0] = 255; - rgba[1] = 255; - rgba[2] = 255; - rgba[3] = 255; - } - - void set(const std::string& text, unsigned char* rgba) - { - this->text = text; - ::memcpy((void*)this->rgba, (const void*)rgba, 4 * sizeof(unsigned char)); - } - }; - - if (config == nullptr) - return; - - unsigned char rgba[4]; - std::vector colors; - - if (static_cast(config->opt_int("printer_technology")) == ptSLA) - { + if (static_cast(config->opt_int("printer_technology")) == ptSLA) { const std::string& txt_color = config->opt_string("material_colour").empty() ? print_config_def.get("material_colour")->get_default_value()->value : config->opt_string("material_colour"); - if (Slic3r::GUI::BitmapCache::parse_color4(txt_color, rgba)) { - colors.resize(1); - colors[0].set(txt_color, rgba); - } + ColorRGBA rgba; + if (decode_color(txt_color, rgba)) + colors.push_back({ txt_color, rgba }); } - else - { + else { const ConfigOptionStrings* filamemts_opt = dynamic_cast(config->option("filament_colour")); if (filamemts_opt == nullptr) return; - unsigned int colors_count = (unsigned int)filamemts_opt->values.size(); + size_t colors_count = (size_t)filamemts_opt->values.size(); if (colors_count == 0) return; colors.resize(colors_count); for (unsigned int i = 0; i < colors_count; ++i) { - const std::string& txt_color = config->opt_string("filament_colour", i); - if (Slic3r::GUI::BitmapCache::parse_color4(txt_color, rgba)) - colors[i].set(txt_color, rgba); + ColorRGBA rgba; + const std::string& fil_color = config->opt_string("filament_colour", i); + if (decode_color(fil_color, rgba)) + colors[i] = { fil_color, rgba }; } } for (GLVolume* volume : volumes) { - if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) + if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0) continue; int extruder_id = volume->extruder_id - 1; if (extruder_id < 0 || (int)colors.size() <= extruder_id) extruder_id = 0; - const Color& color = colors[extruder_id]; - if (!color.text.empty()) { - for (int i = 0; i < 4; ++i) { - if (is_update_alpha == false) { - if (i < 3) { - volume->color[i] = (float) color.rgba[i] * inv_255; - } - continue; - } - volume->color[i] = (float) color.rgba[i] * inv_255; - } - } + const ColorItem& color = colors[extruder_id]; + if (!color.first.empty()) { + if (!is_update_alpha) { + float old_a = color.second.a(); + volume->color = color.second; + volume->color.a(old_a); + } + volume->color = color.second; + } } } @@ -1625,7 +1168,7 @@ void GLVolumeCollection::set_transparency(float alpha) if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) continue; - volume->color[3] = alpha; + volume->color.a(alpha); } } @@ -1677,60 +1220,63 @@ std::string GLVolumeCollection::log_memory_info() const return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; } -// caller is responsible for supplying NO lines with zero length -static void thick_lines_to_indexed_vertex_array( - const Lines &lines, - const std::vector &widths, - const std::vector &heights, - bool closed, - double top_z, - GLIndexedVertexArray &volume) +static void thick_lines_to_geometry( + const Lines& lines, + const std::vector& widths, + const std::vector& heights, + bool closed, + double top_z, + GUI::GLModel::Geometry& geometry) { - assert(! lines.empty()); + assert(!lines.empty()); if (lines.empty()) return; -#define LEFT 0 -#define RIGHT 1 -#define TOP 2 -#define BOTTOM 3 + enum Direction : unsigned char + { + Left, + Right, + Top, + Bottom + }; // right, left, top, bottom - int idx_prev[4] = { -1, -1, -1, -1 }; - double bottom_z_prev = 0.; - Vec2d b1_prev(Vec2d::Zero()); - Vec2d v_prev(Vec2d::Zero()); - int idx_initial[4] = { -1, -1, -1, -1 }; - double width_initial = 0.; - double bottom_z_initial = 0.0; - double len_prev = 0.0; + std::array idx_prev = { -1, -1, -1, -1 }; + std::array idx_initial = { -1, -1, -1, -1 }; + + double bottom_z_prev = 0.0; + Vec2d b1_prev(Vec2d::Zero()); + Vec2d v_prev(Vec2d::Zero()); + double len_prev = 0.0; + double width_initial = 0.0; + double bottom_z_initial = 0.0; // loop once more in case of closed loops - size_t lines_end = closed ? (lines.size() + 1) : lines.size(); - for (size_t ii = 0; ii < lines_end; ++ ii) { - size_t i = (ii == lines.size()) ? 0 : ii; - const Line &line = lines[i]; - double bottom_z = top_z - heights[i]; - double middle_z = 0.5 * (top_z + bottom_z); - double width = widths[i]; + const size_t lines_end = closed ? (lines.size() + 1) : lines.size(); + for (size_t ii = 0; ii < lines_end; ++ii) { + const size_t i = (ii == lines.size()) ? 0 : ii; + const Line& line = lines[i]; + const double bottom_z = top_z - heights[i]; + const double middle_z = 0.5 * (top_z + bottom_z); + const double width = widths[i]; - bool is_first = (ii == 0); - bool is_last = (ii == lines_end - 1); - bool is_closing = closed && is_last; + const bool is_first = (ii == 0); + const bool is_last = (ii == lines_end - 1); + const bool is_closing = closed && is_last; - Vec2d v = unscale(line.vector()).normalized(); - double len = unscale(line.length()); + const Vec2d v = unscale(line.vector()).normalized(); + const double len = unscale(line.length()); - Vec2d a = unscale(line.a); - Vec2d b = unscale(line.b); + const Vec2d a = unscale(line.a); + const Vec2d b = unscale(line.b); Vec2d a1 = a; Vec2d a2 = a; Vec2d b1 = b; Vec2d b2 = b; { - double dist = 0.5 * width; // scaled - double dx = dist * v(0); - double dy = dist * v(1); + const double dist = 0.5 * width; // scaled + const double dx = dist * v.x(); + const double dy = dist * v.y(); a1 += Vec2d(+dy, -dx); a2 += Vec2d(-dy, +dx); b1 += Vec2d(+dy, -dx); @@ -1738,102 +1284,101 @@ static void thick_lines_to_indexed_vertex_array( } // calculate new XY normals - Vec2d xy_right_normal = unscale(line.normal()).normalized(); + const Vec2d xy_right_normal = unscale(line.normal()).normalized(); - int idx_a[4] = { 0, 0, 0, 0 }; // initialized to avoid warnings - int idx_b[4] = { 0, 0, 0, 0 }; // initialized to avoid warnings - int idx_last = int(volume.vertices_and_normals_interleaved.size() / 6); + std::array idx_a = { 0, 0, 0, 0 }; + std::array idx_b = { 0, 0, 0, 0 }; + int idx_last = int(geometry.vertices_count()); - bool bottom_z_different = bottom_z_prev != bottom_z; + const bool bottom_z_different = bottom_z_prev != bottom_z; bottom_z_prev = bottom_z; - if (!is_first && bottom_z_different) - { + if (!is_first && bottom_z_different) { // Found a change of the layer thickness -> Add a cap at the end of the previous segment. - volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); + geometry.add_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]); + geometry.add_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]); } // Share top / bottom vertices if possible. if (is_first) { - idx_a[TOP] = idx_last++; - volume.push_geometry(a(0), a(1), top_z , 0., 0., 1.); - } else { - idx_a[TOP] = idx_prev[TOP]; + idx_a[Top] = idx_last++; + geometry.add_vertex(Vec3f(a.x(), a.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f)); } + else + idx_a[Top] = idx_prev[Top]; if (is_first || bottom_z_different) { // Start of the 1st line segment or a change of the layer thickness while maintaining the print_z. - idx_a[BOTTOM] = idx_last ++; - volume.push_geometry(a(0), a(1), bottom_z, 0., 0., -1.); - idx_a[LEFT ] = idx_last ++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); - idx_a[RIGHT] = idx_last ++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); - } - else { - idx_a[BOTTOM] = idx_prev[BOTTOM]; + idx_a[Bottom] = idx_last++; + geometry.add_vertex(Vec3f(a.x(), a.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f)); + idx_a[Left] = idx_last++; + geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f)); + idx_a[Right] = idx_last++; + geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f)); } + else + idx_a[Bottom] = idx_prev[Bottom]; if (is_first) { // Start of the 1st line segment. - width_initial = width; + width_initial = width; bottom_z_initial = bottom_z; - memcpy(idx_initial, idx_a, sizeof(int) * 4); - } else { + idx_initial = idx_a; + } + else { // Continuing a previous segment. // Share left / right vertices if possible. - double v_dot = v_prev.dot(v); + const double v_dot = v_prev.dot(v); // To reduce gpu memory usage, we try to reuse vertices - // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges // is longer than a fixed threshold. // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts - double len_threshold = 2.5; + const double len_threshold = 2.5; // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met - bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); + const bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); if (sharp) { - if (!bottom_z_different) - { + if (!bottom_z_different) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. - idx_a[RIGHT] = idx_last++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); - idx_a[LEFT] = idx_last++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); - if (cross2(v_prev, v) > 0.) { + idx_a[Right] = idx_last++; + geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f)); + idx_a[Left] = idx_last++; + geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f)); + if (cross2(v_prev, v) > 0.0) { // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + geometry.add_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]); + geometry.add_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]); } else { // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); - volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + geometry.add_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]); + geometry.add_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]); } } } - else - { - if (!bottom_z_different) - { + else { + if (!bottom_z_different) { // The two successive segments are nearly collinear. - idx_a[LEFT ] = idx_prev[LEFT]; - idx_a[RIGHT] = idx_prev[RIGHT]; + idx_a[Left] = idx_prev[Left]; + idx_a[Right] = idx_prev[Right]; } } if (is_closing) { if (!sharp) { - if (!bottom_z_different) - { + if (!bottom_z_different) { // Closing a loop with smooth transition. Unify the closing left / right vertices. - memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT ] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6, sizeof(float) * 6); - memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6); - volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end()); + geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left])); + geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right])); + geometry.remove_vertex(geometry.vertices_count() - 1); + geometry.remove_vertex(geometry.vertices_count() - 1); // Replace the left / right vertex indices to point to the start of the loop. - for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++ u) { - if (volume.quad_indices[u] == idx_prev[LEFT]) - volume.quad_indices[u] = idx_initial[LEFT]; - else if (volume.quad_indices[u] == idx_prev[RIGHT]) - volume.quad_indices[u] = idx_initial[RIGHT]; + const size_t indices_count = geometry.indices_count(); + for (size_t u = indices_count - 24; u < indices_count; ++u) { + const unsigned int id = geometry.extract_index(u); + if (id == (unsigned int)idx_prev[Left]) + geometry.set_index(u, (unsigned int)idx_initial[Left]); + else if (id == (unsigned int)idx_prev[Right]) + geometry.set_index(u, (unsigned int)idx_initial[Right]); } } } @@ -1843,235 +1388,232 @@ static void thick_lines_to_indexed_vertex_array( } // Only new allocate top / bottom vertices, if not closing a loop. - if (is_closing) { - idx_b[TOP] = idx_initial[TOP]; - } else { - idx_b[TOP] = idx_last ++; - volume.push_geometry(b(0), b(1), top_z , 0., 0., 1.); + if (is_closing) + idx_b[Top] = idx_initial[Top]; + else { + idx_b[Top] = idx_last++; + geometry.add_vertex(Vec3f(b.x(), b.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f)); } - if (is_closing && (width == width_initial) && (bottom_z == bottom_z_initial)) { - idx_b[BOTTOM] = idx_initial[BOTTOM]; - } else { - idx_b[BOTTOM] = idx_last ++; - volume.push_geometry(b(0), b(1), bottom_z, 0., 0., -1.); + if (is_closing && width == width_initial && bottom_z == bottom_z_initial) + idx_b[Bottom] = idx_initial[Bottom]; + else { + idx_b[Bottom] = idx_last++; + geometry.add_vertex(Vec3f(b.x(), b.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f)); } // Generate new vertices for the end of this line segment. - idx_b[LEFT ] = idx_last ++; - volume.push_geometry(b2(0), b2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); - idx_b[RIGHT ] = idx_last ++; - volume.push_geometry(b1(0), b1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); + idx_b[Left] = idx_last++; + geometry.add_vertex(Vec3f(b2.x(), b2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f)); + idx_b[Right] = idx_last++; + geometry.add_vertex(Vec3f(b1.x(), b1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f)); - memcpy(idx_prev, idx_b, 4 * sizeof(int)); + idx_prev = idx_b; bottom_z_prev = bottom_z; b1_prev = b1; v_prev = v; len_prev = len; - if (bottom_z_different && (closed || (!is_first && !is_last))) - { + if (bottom_z_different && (closed || (!is_first && !is_last))) { // Found a change of the layer thickness -> Add a cap at the beginning of this segment. - volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); + geometry.add_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]); + geometry.add_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]); } - if (! closed) { + if (!closed) { // Terminate open paths with caps. - if (is_first) - volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); + if (is_first) { + geometry.add_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]); + geometry.add_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]); + } // We don't use 'else' because both cases are true if we have only one line. - if (is_last) - volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); + if (is_last) { + geometry.add_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]); + geometry.add_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]); + } } // Add quads for a straight hollow tube-like segment. // bottom-right face - volume.push_quad(idx_a[BOTTOM], idx_b[BOTTOM], idx_b[RIGHT], idx_a[RIGHT]); + geometry.add_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]); + geometry.add_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]); // top-right face - volume.push_quad(idx_a[RIGHT], idx_b[RIGHT], idx_b[TOP], idx_a[TOP]); + geometry.add_triangle(idx_a[Right], idx_b[Right], idx_b[Top]); + geometry.add_triangle(idx_a[Right], idx_b[Top], idx_a[Top]); // top-left face - volume.push_quad(idx_a[TOP], idx_b[TOP], idx_b[LEFT], idx_a[LEFT]); + geometry.add_triangle(idx_a[Top], idx_b[Top], idx_b[Left]); + geometry.add_triangle(idx_a[Top], idx_b[Left], idx_a[Left]); // bottom-left face - volume.push_quad(idx_a[LEFT], idx_b[LEFT], idx_b[BOTTOM], idx_a[BOTTOM]); + geometry.add_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]); + geometry.add_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]); } - -#undef LEFT -#undef RIGHT -#undef TOP -#undef BOTTOM } // caller is responsible for supplying NO lines with zero length -static void thick_lines_to_indexed_vertex_array(const Lines3& lines, +static void thick_lines_to_geometry( + const Lines3& lines, const std::vector& widths, const std::vector& heights, - bool closed, - GLIndexedVertexArray& volume) + bool closed, + GUI::GLModel::Geometry& geometry) { assert(!lines.empty()); if (lines.empty()) return; -#define LEFT 0 -#define RIGHT 1 -#define TOP 2 -#define BOTTOM 3 + enum Direction : unsigned char + { + Left, + Right, + Top, + Bottom + }; // left, right, top, bottom - int idx_initial[4] = { -1, -1, -1, -1 }; - int idx_prev[4] = { -1, -1, -1, -1 }; - double z_prev = 0.0; - double len_prev = 0.0; - Vec3d n_right_prev = Vec3d::Zero(); - Vec3d n_top_prev = Vec3d::Zero(); - Vec3d unit_v_prev = Vec3d::Zero(); - double width_initial = 0.0; + std::array idx_prev = { -1, -1, -1, -1 }; + std::array idx_initial = { -1, -1, -1, -1 }; + + double z_prev = 0.0; + double len_prev = 0.0; + Vec3d n_right_prev = Vec3d::Zero(); + Vec3d n_top_prev = Vec3d::Zero(); + Vec3d unit_v_prev = Vec3d::Zero(); + double width_initial = 0.0; // new vertices around the line endpoints // left, right, top, bottom - Vec3d a[4] = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; - Vec3d b[4] = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; + std::array a = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; + std::array b = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; // loop once more in case of closed loops - size_t lines_end = closed ? (lines.size() + 1) : lines.size(); - for (size_t ii = 0; ii < lines_end; ++ii) - { - size_t i = (ii == lines.size()) ? 0 : ii; + const size_t lines_end = closed ? (lines.size() + 1) : lines.size(); + for (size_t ii = 0; ii < lines_end; ++ii) { + const size_t i = (ii == lines.size()) ? 0 : ii; const Line3& line = lines[i]; - double height = heights[i]; - double width = widths[i]; + const double height = heights[i]; + const double width = widths[i]; - Vec3d unit_v = unscale(line.vector()).normalized(); - double len = unscale(line.length()); + const Vec3d unit_v = unscale(line.vector()).normalized(); + const double len = unscale(line.length()); Vec3d n_top = Vec3d::Zero(); Vec3d n_right = Vec3d::Zero(); - if ((line.a(0) == line.b(0)) && (line.a(1) == line.b(1))) - { + if (line.a.x() == line.b.x() && line.a.y() == line.b.y()) { // vertical segment n_top = Vec3d::UnitY(); n_right = Vec3d::UnitX(); - if (line.a(2) < line.b(2)) + if (line.a.z() < line.b.z()) n_right = -n_right; } - else - { + else { // horizontal segment n_right = unit_v.cross(Vec3d::UnitZ()).normalized(); n_top = n_right.cross(unit_v).normalized(); } - Vec3d rl_displacement = 0.5 * width * n_right; - Vec3d tb_displacement = 0.5 * height * n_top; - Vec3d l_a = unscale(line.a); - Vec3d l_b = unscale(line.b); + const Vec3d rl_displacement = 0.5 * width * n_right; + const Vec3d tb_displacement = 0.5 * height * n_top; + const Vec3d l_a = unscale(line.a); + const Vec3d l_b = unscale(line.b); - a[RIGHT] = l_a + rl_displacement; - a[LEFT] = l_a - rl_displacement; - a[TOP] = l_a + tb_displacement; - a[BOTTOM] = l_a - tb_displacement; - b[RIGHT] = l_b + rl_displacement; - b[LEFT] = l_b - rl_displacement; - b[TOP] = l_b + tb_displacement; - b[BOTTOM] = l_b - tb_displacement; + a[Right] = l_a + rl_displacement; + a[Left] = l_a - rl_displacement; + a[Top] = l_a + tb_displacement; + a[Bottom] = l_a - tb_displacement; + b[Right] = l_b + rl_displacement; + b[Left] = l_b - rl_displacement; + b[Top] = l_b + tb_displacement; + b[Bottom] = l_b - tb_displacement; - Vec3d n_bottom = -n_top; - Vec3d n_left = -n_right; + const Vec3d n_bottom = -n_top; + const Vec3d n_left = -n_right; - int idx_a[4]; - int idx_b[4]; - int idx_last = int(volume.vertices_and_normals_interleaved.size() / 6); + std::array idx_a = { 0, 0, 0, 0}; + std::array idx_b = { 0, 0, 0, 0 }; + int idx_last = int(geometry.vertices_count()); - bool z_different = (z_prev != l_a(2)); - z_prev = l_b(2); + const bool z_different = (z_prev != l_a.z()); + z_prev = l_b.z(); // Share top / bottom vertices if possible. - if (ii == 0) - { - idx_a[TOP] = idx_last++; - volume.push_geometry(a[TOP], n_top); + if (ii == 0) { + idx_a[Top] = idx_last++; + geometry.add_vertex((Vec3f)a[Top].cast(), (Vec3f)n_top.cast()); } else - idx_a[TOP] = idx_prev[TOP]; + idx_a[Top] = idx_prev[Top]; - if ((ii == 0) || z_different) - { + if (ii == 0 || z_different) { // Start of the 1st line segment or a change of the layer thickness while maintaining the print_z. - idx_a[BOTTOM] = idx_last++; - volume.push_geometry(a[BOTTOM], n_bottom); - idx_a[LEFT] = idx_last++; - volume.push_geometry(a[LEFT], n_left); - idx_a[RIGHT] = idx_last++; - volume.push_geometry(a[RIGHT], n_right); + idx_a[Bottom] = idx_last++; + geometry.add_vertex((Vec3f)a[Bottom].cast(), (Vec3f)n_bottom.cast()); + idx_a[Left] = idx_last++; + geometry.add_vertex((Vec3f)a[Left].cast(), (Vec3f)n_left.cast()); + idx_a[Right] = idx_last++; + geometry.add_vertex((Vec3f)a[Right].cast(), (Vec3f)n_right.cast()); } else - idx_a[BOTTOM] = idx_prev[BOTTOM]; + idx_a[Bottom] = idx_prev[Bottom]; - if (ii == 0) - { + if (ii == 0) { // Start of the 1st line segment. width_initial = width; - ::memcpy(idx_initial, idx_a, sizeof(int) * 4); + idx_initial = idx_a; } - else - { + else { // Continuing a previous segment. // Share left / right vertices if possible. - double v_dot = unit_v_prev.dot(unit_v); - bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0; + const double v_dot = unit_v_prev.dot(unit_v); + const bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0; // To reduce gpu memory usage, we try to reuse vertices - // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges // is longer than a fixed threshold. // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts - double len_threshold = 2.5; + const double len_threshold = 2.5; // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met - bool is_sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); - if (is_sharp) - { + const bool is_sharp = v_dot < 0.707 || len_prev > len_threshold || len > len_threshold; + if (is_sharp) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. - idx_a[RIGHT] = idx_last++; - volume.push_geometry(a[RIGHT], n_right); - idx_a[LEFT] = idx_last++; - volume.push_geometry(a[LEFT], n_left); + idx_a[Right] = idx_last++; + geometry.add_vertex((Vec3f)a[Right].cast(), (Vec3f)n_right.cast()); + idx_a[Left] = idx_last++; + geometry.add_vertex((Vec3f)a[Left].cast(), (Vec3f)n_left.cast()); - if (is_right_turn) - { + if (is_right_turn) { // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + geometry.add_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]); + geometry.add_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]); } - else - { + else { // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); - volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + geometry.add_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]); + geometry.add_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]); } } - else - { + else { // The two successive segments are nearly collinear. - idx_a[LEFT] = idx_prev[LEFT]; - idx_a[RIGHT] = idx_prev[RIGHT]; + idx_a[Left] = idx_prev[Left]; + idx_a[Right] = idx_prev[Right]; } - if (ii == lines.size()) - { - if (!is_sharp) - { + if (ii == lines.size()) { + if (!is_sharp) { // Closing a loop with smooth transition. Unify the closing left / right vertices. - ::memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT] * 6, sizeof(float) * 6); - ::memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6); - volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end()); + geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left])); + geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right])); + geometry.remove_vertex(geometry.vertices_count() - 1); + geometry.remove_vertex(geometry.vertices_count() - 1); // Replace the left / right vertex indices to point to the start of the loop. - for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++u) - { - if (volume.quad_indices[u] == idx_prev[LEFT]) - volume.quad_indices[u] = idx_initial[LEFT]; - else if (volume.quad_indices[u] == idx_prev[RIGHT]) - volume.quad_indices[u] = idx_initial[RIGHT]; + const size_t indices_count = geometry.indices_count(); + for (size_t u = indices_count - 24; u < indices_count; ++u) { + const unsigned int id = geometry.extract_index(u); + if (id == (unsigned int)idx_prev[Left]) + geometry.set_index(u, (unsigned int)idx_initial[Left]); + else if (id == (unsigned int)idx_prev[Right]) + geometry.set_index(u, (unsigned int)idx_initial[Right]); } } @@ -2081,246 +1623,161 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, } // Only new allocate top / bottom vertices, if not closing a loop. - if (closed && (ii + 1 == lines.size())) - idx_b[TOP] = idx_initial[TOP]; - else - { - idx_b[TOP] = idx_last++; - volume.push_geometry(b[TOP], n_top); + if (closed && ii + 1 == lines.size()) + idx_b[Top] = idx_initial[Top]; + else { + idx_b[Top] = idx_last++; + geometry.add_vertex((Vec3f)b[Top].cast(), (Vec3f)n_top.cast()); } - if (closed && (ii + 1 == lines.size()) && (width == width_initial)) - idx_b[BOTTOM] = idx_initial[BOTTOM]; - else - { - idx_b[BOTTOM] = idx_last++; - volume.push_geometry(b[BOTTOM], n_bottom); + if (closed && ii + 1 == lines.size() && width == width_initial) + idx_b[Bottom] = idx_initial[Bottom]; + else { + idx_b[Bottom] = idx_last++; + geometry.add_vertex((Vec3f)b[Bottom].cast(), (Vec3f)n_bottom.cast()); } // Generate new vertices for the end of this line segment. - idx_b[LEFT] = idx_last++; - volume.push_geometry(b[LEFT], n_left); - idx_b[RIGHT] = idx_last++; - volume.push_geometry(b[RIGHT], n_right); + idx_b[Left] = idx_last++; + geometry.add_vertex((Vec3f)b[Left].cast(), (Vec3f)n_left.cast()); + idx_b[Right] = idx_last++; + geometry.add_vertex((Vec3f)b[Right].cast(), (Vec3f)n_right.cast()); - ::memcpy(idx_prev, idx_b, 4 * sizeof(int)); + idx_prev = idx_b; n_right_prev = n_right; n_top_prev = n_top; unit_v_prev = unit_v; len_prev = len; - if (!closed) - { + if (!closed) { // Terminate open paths with caps. - if (i == 0) - volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); + if (i == 0) { + geometry.add_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]); + geometry.add_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]); + } // We don't use 'else' because both cases are true if we have only one line. - if (i + 1 == lines.size()) - volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); + if (i + 1 == lines.size()) { + geometry.add_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]); + geometry.add_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]); + } } // Add quads for a straight hollow tube-like segment. // bottom-right face - volume.push_quad(idx_a[BOTTOM], idx_b[BOTTOM], idx_b[RIGHT], idx_a[RIGHT]); + geometry.add_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]); + geometry.add_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]); // top-right face - volume.push_quad(idx_a[RIGHT], idx_b[RIGHT], idx_b[TOP], idx_a[TOP]); + geometry.add_triangle(idx_a[Right], idx_b[Right], idx_b[Top]); + geometry.add_triangle(idx_a[Right], idx_b[Top], idx_a[Top]); // top-left face - volume.push_quad(idx_a[TOP], idx_b[TOP], idx_b[LEFT], idx_a[LEFT]); + geometry.add_triangle(idx_a[Top], idx_b[Top], idx_b[Left]); + geometry.add_triangle(idx_a[Top], idx_b[Left], idx_a[Left]); // bottom-left face - volume.push_quad(idx_a[LEFT], idx_b[LEFT], idx_b[BOTTOM], idx_a[BOTTOM]); + geometry.add_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]); + geometry.add_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]); } - -#undef LEFT -#undef RIGHT -#undef TOP -#undef BOTTOM -} - -static void point_to_indexed_vertex_array(const Vec3crd& point, - double width, - double height, - GLIndexedVertexArray& volume) -{ - // builds a double piramid, with vertices on the local axes, around the point - - Vec3d center = unscale(point); - - double scale_factor = 1.0; - double w = scale_factor * width; - double h = scale_factor * height; - - // new vertices ids - int idx_last = int(volume.vertices_and_normals_interleaved.size() / 6); - int idxs[6]; - for (int i = 0; i < 6; ++i) - { - idxs[i] = idx_last + i; - } - - Vec3d displacement_x(w, 0.0, 0.0); - Vec3d displacement_y(0.0, w, 0.0); - Vec3d displacement_z(0.0, 0.0, h); - - Vec3d unit_x(1.0, 0.0, 0.0); - Vec3d unit_y(0.0, 1.0, 0.0); - Vec3d unit_z(0.0, 0.0, 1.0); - - // vertices - volume.push_geometry(center - displacement_x, -unit_x); // idxs[0] - volume.push_geometry(center + displacement_x, unit_x); // idxs[1] - volume.push_geometry(center - displacement_y, -unit_y); // idxs[2] - volume.push_geometry(center + displacement_y, unit_y); // idxs[3] - volume.push_geometry(center - displacement_z, -unit_z); // idxs[4] - volume.push_geometry(center + displacement_z, unit_z); // idxs[5] - - // top piramid faces - volume.push_triangle(idxs[0], idxs[2], idxs[5]); - volume.push_triangle(idxs[2], idxs[1], idxs[5]); - volume.push_triangle(idxs[1], idxs[3], idxs[5]); - volume.push_triangle(idxs[3], idxs[0], idxs[5]); - - // bottom piramid faces - volume.push_triangle(idxs[2], idxs[0], idxs[4]); - volume.push_triangle(idxs[1], idxs[2], idxs[4]); - volume.push_triangle(idxs[3], idxs[1], idxs[4]); - volume.push_triangle(idxs[0], idxs[3], idxs[4]); } void _3DScene::thick_lines_to_verts( - const Lines &lines, - const std::vector &widths, - const std::vector &heights, - bool closed, - double top_z, - GLVolume &volume) -{ - thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, top_z, volume.indexed_vertex_array); -} - -void _3DScene::thick_lines_to_verts(const Lines3& lines, + const Lines& lines, const std::vector& widths, const std::vector& heights, - bool closed, - GLVolume& volume) + bool closed, + double top_z, + GUI::GLModel::Geometry& geometry) { - thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, volume.indexed_vertex_array); + thick_lines_to_geometry(lines, widths, heights, closed, top_z, geometry); } -static void thick_point_to_verts(const Vec3crd& point, - double width, - double height, - GLVolume& volume) +void _3DScene::thick_lines_to_verts( + const Lines3& lines, + const std::vector& widths, + const std::vector& heights, + bool closed, + GUI::GLModel::Geometry& geometry) { - point_to_indexed_vertex_array(point, width, height, volume.indexed_vertex_array); -} - -void _3DScene::extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume) -{ - if (polyline.size() >= 2) { - size_t num_segments = polyline.size() - 1; - thick_lines_to_verts(polyline.lines(), std::vector(num_segments, width), std::vector(num_segments, height), false, print_z, volume); - } + thick_lines_to_geometry(lines, widths, heights, closed, geometry); } // Fill in the qverts and tverts with quads and triangles for the extrusion_path. -void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume) -{ - extrusionentity_to_verts(extrusion_path.polyline, extrusion_path.width, extrusion_path.height, print_z, volume); -} - -// Fill in the qverts and tverts with quads and triangles for the extrusion_path. -void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) { Polyline polyline = extrusion_path.polyline; polyline.remove_duplicate_points(); polyline.translate(copy); - Lines lines = polyline.lines(); + const Lines lines = polyline.lines(); std::vector widths(lines.size(), extrusion_path.width); std::vector heights(lines.size(), extrusion_path.height); - thick_lines_to_verts(lines, widths, heights, false, print_z, volume); + thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); } // Fill in the qverts and tverts with quads and triangles for the extrusion_loop. -void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) { Lines lines; std::vector widths; std::vector heights; - for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) { + for (const ExtrusionPath& extrusion_path : extrusion_loop.paths) { Polyline polyline = extrusion_path.polyline; polyline.remove_duplicate_points(); polyline.translate(copy); - Lines lines_this = polyline.lines(); + const Lines lines_this = polyline.lines(); append(lines, lines_this); widths.insert(widths.end(), lines_this.size(), extrusion_path.width); heights.insert(heights.end(), lines_this.size(), extrusion_path.height); } - thick_lines_to_verts(lines, widths, heights, true, print_z, volume); + thick_lines_to_verts(lines, widths, heights, true, print_z, geometry); } // Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path. -void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) { Lines lines; std::vector widths; std::vector heights; - for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) { + for (const ExtrusionPath& extrusion_path : extrusion_multi_path.paths) { Polyline polyline = extrusion_path.polyline; polyline.remove_duplicate_points(); polyline.translate(copy); - Lines lines_this = polyline.lines(); + const Lines lines_this = polyline.lines(); append(lines, lines_this); widths.insert(widths.end(), lines_this.size(), extrusion_path.width); heights.insert(heights.end(), lines_this.size(), extrusion_path.height); } - thick_lines_to_verts(lines, widths, heights, false, print_z, volume); + thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); } -void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) { - for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities) - extrusionentity_to_verts(extrusion_entity, print_z, copy, volume); + for (const ExtrusionEntity* extrusion_entity : extrusion_entity_collection.entities) + extrusionentity_to_verts(extrusion_entity, print_z, copy, geometry); } -void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) { if (extrusion_entity != nullptr) { - auto *extrusion_path = dynamic_cast(extrusion_entity); + auto* extrusion_path = dynamic_cast(extrusion_entity); if (extrusion_path != nullptr) - extrusionentity_to_verts(*extrusion_path, print_z, copy, volume); + extrusionentity_to_verts(*extrusion_path, print_z, copy, geometry); else { - auto *extrusion_loop = dynamic_cast(extrusion_entity); + auto* extrusion_loop = dynamic_cast(extrusion_entity); if (extrusion_loop != nullptr) - extrusionentity_to_verts(*extrusion_loop, print_z, copy, volume); + extrusionentity_to_verts(*extrusion_loop, print_z, copy, geometry); else { - auto *extrusion_multi_path = dynamic_cast(extrusion_entity); + auto* extrusion_multi_path = dynamic_cast(extrusion_entity); if (extrusion_multi_path != nullptr) - extrusionentity_to_verts(*extrusion_multi_path, print_z, copy, volume); + extrusionentity_to_verts(*extrusion_multi_path, print_z, copy, geometry); else { - auto *extrusion_entity_collection = dynamic_cast(extrusion_entity); + auto* extrusion_entity_collection = dynamic_cast(extrusion_entity); if (extrusion_entity_collection != nullptr) - extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume); - else { + extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, geometry); + else throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()"); - } } } } } } -void _3DScene::polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume) -{ - Lines3 lines = polyline.lines(); - std::vector widths(lines.size(), width); - std::vector heights(lines.size(), height); - thick_lines_to_verts(lines, widths, heights, false, volume); -} - -void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume) -{ - thick_point_to_verts(point, width, height, volume); -} - } // namespace Slic3r diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index c850884da8..ce10a463f2 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -1,3 +1,14 @@ +///|/ Copyright (c) Prusa Research 2017 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, David Kocík @kocikdav, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2017 Eyal Soha @eyal0 +///|/ Copyright (c) Slic3r 2015 Alessandro Ranellucci @alranel +///|/ +///|/ ported from lib/Slic3r/GUI/3DScene.pm: +///|/ Copyright (c) Prusa Research 2016 - 2019 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka +///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2013 Guillaume Seguin @iXce +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_3DScene_hpp_ #define slic3r_3DScene_hpp_ @@ -7,11 +18,13 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/Color.hpp" // BBS #include "libslic3r/ObjectID.hpp" #include "GLModel.hpp" #include "GLShader.hpp" +#include "MeshUtils.hpp" #include #include @@ -30,10 +43,10 @@ #define glsafe(cmd) cmd #define glcheck() #endif // HAS_GLSAFE -extern std::vector> get_extruders_colors(); -extern float FullyTransparentMaterialThreshold; -extern float FullTransparentModdifiedToFixAlpha; -extern std::array adjust_color_for_rendering(const std::array &colors); +extern std::vector get_extruders_colors(); +extern float FullyTransparentMaterialThreshold; +extern float FullTransparentModdifiedToFixAlpha; +extern Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors); namespace Slic3r { @@ -54,224 +67,23 @@ enum ModelInstanceEPrintVolumeState : unsigned char; using ModelObjectPtrs = std::vector; // Return appropriate color based on the ModelVolume. -std::array color_from_model_volume(const ModelVolume& model_volume); - -// A container for interleaved arrays of 3D vertices and normals, -// possibly indexed by triangles and / or quads. -class GLIndexedVertexArray { -public: - // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized. - static_assert(sizeof(Eigen::AlignedBox) == 24, "Eigen::AlignedBox is not being vectorized, thus it does not need to be aligned"); - using BoundingBox = Eigen::AlignedBox; - - GLIndexedVertexArray() { m_bounding_box.setEmpty(); } - GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : - vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), - triangle_indices(rhs.triangle_indices), - quad_indices(rhs.quad_indices), - m_bounding_box(rhs.m_bounding_box) - { assert(!rhs.has_VBOs()); m_bounding_box.setEmpty(); } - GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : - vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), - triangle_indices(std::move(rhs.triangle_indices)), - quad_indices(std::move(rhs.quad_indices)), - m_bounding_box(rhs.m_bounding_box) - { assert(! rhs.has_VBOs()); } - - ~GLIndexedVertexArray() { release_geometry(); } - - GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) - { - assert(vertices_and_normals_interleaved_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); - assert(rhs.triangle_indices_VBO_id == 0); - assert(rhs.quad_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; - this->triangle_indices = rhs.triangle_indices; - this->quad_indices = rhs.quad_indices; - this->m_bounding_box = rhs.m_bounding_box; - this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - this->triangle_indices_size = rhs.triangle_indices_size; - this->quad_indices_size = rhs.quad_indices_size; - return *this; - } - - GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs) - { - assert(vertices_and_normals_interleaved_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); - assert(rhs.triangle_indices_VBO_id == 0); - assert(rhs.quad_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); - this->triangle_indices = std::move(rhs.triangle_indices); - this->quad_indices = std::move(rhs.quad_indices); - this->m_bounding_box = rhs.m_bounding_box; - this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - this->triangle_indices_size = rhs.triangle_indices_size; - this->quad_indices_size = rhs.quad_indices_size; - return *this; - } - - // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) - std::vector vertices_and_normals_interleaved; - std::vector triangle_indices; - std::vector quad_indices; - - // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, - // the above mentioned std::vectors are cleared and the following variables keep their original length. - size_t vertices_and_normals_interleaved_size{ 0 }; - size_t triangle_indices_size{ 0 }; - size_t quad_indices_size{ 0 }; - - // IDs of the Vertex Array Objects, into which the geometry has been loaded. - // Zero if the VBOs are not sent to GPU yet. - unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; - unsigned int triangle_indices_VBO_id{ 0 }; - unsigned int quad_indices_VBO_id{ 0 }; - -#if ENABLE_SMOOTH_NORMALS - void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false); - void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); } -#else - void load_mesh_full_shading(const TriangleMesh& mesh); - void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } -#endif // ENABLE_SMOOTH_NORMALS - - void load_its_flat_shading(const indexed_triangle_set &its); - - inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } - - inline void reserve(size_t sz) { - this->vertices_and_normals_interleaved.reserve(sz * 6); - this->triangle_indices.reserve(sz * 3); - this->quad_indices.reserve(sz * 4); - } - - inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) - this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); - this->vertices_and_normals_interleaved.emplace_back(nx); - this->vertices_and_normals_interleaved.emplace_back(ny); - this->vertices_and_normals_interleaved.emplace_back(nz); - this->vertices_and_normals_interleaved.emplace_back(x); - this->vertices_and_normals_interleaved.emplace_back(y); - this->vertices_and_normals_interleaved.emplace_back(z); - - this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); - m_bounding_box.extend(Vec3f(x, y, z)); - }; - - inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { - push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz)); - } - - template - inline void push_geometry(const Eigen::MatrixBase& p, const Eigen::MatrixBase& n) { - push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2))); - } - - inline void push_triangle(int idx1, int idx2, int idx3) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) - this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); - this->triangle_indices.emplace_back(idx1); - this->triangle_indices.emplace_back(idx2); - this->triangle_indices.emplace_back(idx3); - this->triangle_indices_size = this->triangle_indices.size(); - }; - - inline void push_quad(int idx1, int idx2, int idx3, int idx4) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) - this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); - this->quad_indices.emplace_back(idx1); - this->quad_indices.emplace_back(idx2); - this->quad_indices.emplace_back(idx3); - this->quad_indices.emplace_back(idx4); - this->quad_indices_size = this->quad_indices.size(); - }; - - // Finalize the initialization of the geometry & indices, - // upload the geometry and indices to OpenGL VBO objects - // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool opengl_initialized); - // Release the geometry data, release OpenGL VBOs. - void release_geometry(); - - void render() const; - void render(const std::pair& tverts_range, const std::pair& qverts_range) const; - - // Is there any geometry data stored? - bool empty() const { return vertices_and_normals_interleaved_size == 0; } - - void clear() { - this->vertices_and_normals_interleaved.clear(); - this->triangle_indices.clear(); - this->quad_indices.clear(); - vertices_and_normals_interleaved_size = 0; - triangle_indices_size = 0; - quad_indices_size = 0; - m_bounding_box.setEmpty(); - } - - // Shrink the internal storage to tighly fit the data stored. - void shrink_to_fit() { - this->vertices_and_normals_interleaved.shrink_to_fit(); - this->triangle_indices.shrink_to_fit(); - this->quad_indices.shrink_to_fit(); - } - - const BoundingBox& bounding_box() const { return m_bounding_box; } - - // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const - { - size_t memsize = 0; - if (this->vertices_and_normals_interleaved_VBO_id != 0) - memsize += this->vertices_and_normals_interleaved_size * 4; - if (this->triangle_indices_VBO_id != 0) - memsize += this->triangle_indices_size * 4; - if (this->quad_indices_VBO_id != 0) - memsize += this->quad_indices_size * 4; - return memsize; - } - size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } - -private: - BoundingBox m_bounding_box; -}; +extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); class GLVolume { public: std::string name; - static std::array DISABLED_COLOR; - static std::array SLA_SUPPORT_COLOR; - static std::array SLA_PAD_COLOR; - static std::array NEUTRAL_COLOR; - static std::array UNPRINTABLE_COLOR; - static std::array, 5> MODEL_COLOR; - static std::array MODEL_MIDIFIER_COL; - static std::array MODEL_NEGTIVE_COL; - static std::array SUPPORT_ENFORCER_COL; - static std::array SUPPORT_BLOCKER_COL; - static std::array MODEL_HIDDEN_COL; + static ColorRGBA DISABLED_COLOR; + static ColorRGBA SLA_SUPPORT_COLOR; + static ColorRGBA SLA_PAD_COLOR; + static ColorRGBA NEUTRAL_COLOR; + static ColorRGBA UNPRINTABLE_COLOR; + static std::array MODEL_COLOR; + static ColorRGBA MODEL_MIDIFIER_COL; + static ColorRGBA MODEL_NEGTIVE_COL; + static ColorRGBA SUPPORT_ENFORCER_COL; + static ColorRGBA SUPPORT_BLOCKER_COL; + static ColorRGBA MODEL_HIDDEN_COL; static void update_render_colors(); static void load_render_colors(); @@ -288,7 +100,7 @@ public: }; GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); - GLVolume(const std::array& rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} + GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {} virtual ~GLVolume() = default; // BBS @@ -329,9 +141,9 @@ protected: public: // Color of the triangles / quads held by this volume. - std::array color; + ColorRGBA color; // Color used to render this volume. - std::array render_color; + ColorRGBA render_color; struct CompositeID { CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} @@ -394,20 +206,22 @@ public: bool force_neutral_color : 1; // Whether or not to force rendering of sinking contours bool force_sinking_contours : 1; + // Is render for picking + bool picking : 1; }; // Is mouse or rectangle selection over this object to select/deselect it ? EHoverState hover; - // Interleaved triangles & normals with indexed triangles & quads. - GLIndexedVertexArray indexed_vertex_array; + GUI::GLModel model; + // raycaster used for picking + std::unique_ptr mesh_raycaster; // BBS - mutable std::vector mmuseg_ivas; + mutable std::vector mmuseg_models; mutable ObjectBase::Timestamp mmuseg_ts; // Ranges of triangle and quad indices to be rendered. std::pair tverts_range; - std::pair qverts_range; // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts // of the extrusions per layer. @@ -417,18 +231,11 @@ public: // Bounding box of this volume, in unscaled coordinates. BoundingBoxf3 bounding_box() const { - BoundingBoxf3 out; - if (! this->indexed_vertex_array.bounding_box().isEmpty()) { - out.min = this->indexed_vertex_array.bounding_box().min().cast(); - out.max = this->indexed_vertex_array.bounding_box().max().cast(); - out.defined = true; - }; - return out; + return this->model.get_bounding_box(); } - void set_color(const std::array& rgba); - void set_render_color(float r, float g, float b, float a); - void set_render_color(const std::array& rgba); + void set_color(const ColorRGBA& rgba) { color = rgba; } + void set_render_color(const ColorRGBA& rgba) { render_color = rgba; } // Sets render color in dependence of current state void set_render_color(); // set color according to model volume @@ -517,18 +324,17 @@ public: // convex hull const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } - bool empty() const { return this->indexed_vertex_array.empty(); } + bool empty() const { return this->model.is_empty(); } void set_range(double low, double high); + virtual void render(); + //BBS: add outline related logic and add virtual specifier - virtual void render(bool with_outline = false) const; + virtual void render_with_outline(const Transform3d &view_model_matrix); //BBS: add simple render function for thumbnail - void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector>& extruder_colors) const; - - void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } - void release_geometry() { this->indexed_vertex_array.release_geometry(); } + void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector extruder_colors); void set_bounding_boxes_as_dirty() { m_transformed_bounding_box.reset(); @@ -545,25 +351,26 @@ public: // Return an estimate of the memory consumed by this class. size_t cpu_memory_used() const { - //FIXME what to do wih m_convex_hull? - return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); + return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + + this->offsets.capacity() * sizeof(size_t); } // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } + size_t gpu_memory_used() const { return this->model.gpu_memory_used(); } size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } }; // BBS class GLWipeTowerVolume : public GLVolume { public: - GLWipeTowerVolume(const std::vector>& colors); - virtual void render(bool with_outline = false) const; + GLWipeTowerVolume(const std::vector& colors); + void render() override; + void render_with_outline(const Transform3d &view_model_matrix) override { render(); } - std::vector iva_per_colors; + std::vector model_per_colors; bool IsTransparent(); private: - std::vector> m_colors; + std::vector m_colors; }; typedef std::vector GLVolumePtrs; @@ -599,10 +406,16 @@ private: PrintVolume m_render_volume; // z range for clipping in shaders - float m_z_range[2]; + std::array m_z_range; // plane coeffs for clipping in shaders - float m_clipping_plane[4]; + std::array m_clipping_plane; + + // plane coeffs for render volumes with different colors in shaders + // used by cut gizmo + std::array m_color_clip_plane; + bool m_use_color_clip_plane{ false }; + std::array m_color_clip_plane_colors{ ColorRGBA::RED(), ColorRGBA::BLUE() }; struct Slope { @@ -627,19 +440,15 @@ public: ~GLVolumeCollection() { clear(); } std::vector load_object( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, - const std::vector &instance_idxs, - const std::string &color_by, - bool opengl_initialized); + const std::vector& instance_idxs); int load_object_volume( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, - bool opengl_initialized, bool in_assemble_view = false, bool use_loaded_id = false); @@ -651,31 +460,20 @@ public: const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp, - bool opengl_initialized); + size_t timestamp); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); - GLVolume* new_toolpath_volume(const std::array& rgba, size_t reserve_vbo_floats = 0); - GLVolume* new_nontoolpath_volume(const std::array& rgba, size_t reserve_vbo_floats = 0); + GLVolume* new_toolpath_volume(const ColorRGBA& rgba); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba); int get_selection_support_threshold_angle(bool&) const; // Render the volumes by OpenGL. //BBS: add outline drawing logic - void render(ERenderType type, - bool disable_cullface, - const Transform3d & view_matrix, - std::function filter_func = std::function(), - bool with_outline = true) const; + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix, + std::function filter_func = std::function(), bool with_outline = true) const; - // Finalize the initialization of the geometry & indices, - // upload the geometry and indices to OpenGL VBO objects - // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); } - // Release the geometry data assigned to the volumes. - // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. - void release_geometry() { for (auto *v : volumes) v->release_geometry(); } // Clear the geometry void clear() { for (auto *v : volumes) delete v; volumes.clear(); } @@ -685,7 +483,18 @@ public: void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } - void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; } + void set_clipping_plane(const std::array& coeffs) { m_clipping_plane = coeffs; } + + const std::array& get_z_range() const { return m_z_range; } + const std::array& get_clipping_plane() const { return m_clipping_plane; } + + void set_use_color_clip_plane(bool use) { m_use_color_clip_plane = use; } + void set_color_clip_plane(const Vec3d& cp_normal, double offset) { + for (int i = 0; i < 3; ++i) + m_color_clip_plane[i] = -cp_normal[i]; + m_color_clip_plane[3] = offset; + } + void set_color_clip_plane_colors(const std::array& colors) { m_color_clip_plane_colors = colors; } bool is_slope_GlobalActive() const { return m_slope.isGlobalActive; } bool is_slope_active() const { return m_slope.active; } @@ -726,17 +535,13 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo struct _3DScene { - static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); - static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); - static void extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); - static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); - static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume); + static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry); + static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); }; } diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 74f5efbe6e..e8f21416af 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -2,6 +2,7 @@ #include "I18N.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" @@ -127,10 +128,10 @@ wxString CopyrightsDialog::get_html_text() wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); const auto text_clr = wxGetApp().get_label_clr_default();// wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); - const wxString copyright_str = _(L("Copyright")) + "© "; + const wxString copyright_str = _L("Copyright") + "© "; wxString text = wxString::Format( "" diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 20201faf0f..912faf6bfb 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -18,6 +18,7 @@ namespace GUI { class ConfigOptionsGroup; using ConfigOptionsGroupShp = std::shared_ptr; +using ConfigOptionsGroupWkp = std::weak_ptr; struct BedShape { diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 999d09a7c5..a0edea0711 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -431,46 +431,5 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); } -bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out) -{ - if (scolor.size() == 9) { - unsigned char rgba[4]; - parse_color4(scolor, rgba); - rgb_out[0] = rgba[0]; - rgb_out[1] = rgba[1]; - rgb_out[2] = rgba[2]; - return true; - } - rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; - if (scolor.size() != 7 || scolor.front() != '#') - return false; - const char* c = scolor.data() + 1; - for (size_t i = 0; i < 3; ++i) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - return false; - rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); - } - - return true; -} - -bool BitmapCache::parse_color4(const std::string& scolor, unsigned char* rgba_out) -{ - rgba_out[0] = rgba_out[1] = rgba_out[2] = 0; rgba_out[3] = 255; - if ((scolor.size() != 7 && scolor.size() != 9) || scolor.front() != '#') - return false; - const char* c = scolor.data() + 1; - for (size_t i = 0; i < scolor.size() / 2; ++i) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - return false; - rgba_out[i] = (unsigned char)(digit1 * 16 + digit2); - } - return true; -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 4803fa961d..a5748acac2 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -9,9 +9,11 @@ #include #endif +#include "libslic3r/Color.hpp" struct NSVGimage; -namespace Slic3r { namespace GUI { +namespace Slic3r { +namespace GUI { class BitmapCache { @@ -44,12 +46,9 @@ public: wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = "", const float scale_in_center = 0.f); wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false); - wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } + wxBitmap mksolid(size_t width, size_t height, const ColorRGB& rgb, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } - static bool parse_color(const std::string& scolor, unsigned char* rgb_out); - static bool parse_color4(const std::string& scolor, unsigned char* rgba_out); - private: std::map m_map; double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index cebd49ce75..7206b5e6c4 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -106,6 +106,78 @@ void Camera::select_view(const std::string& direction) } } +double Camera::get_near_left() const +{ + switch (m_type) + { + case EType::Perspective: + return m_frustrum_zs.first * (m_projection_matrix.matrix()(0, 2) - 1.0) / m_projection_matrix.matrix()(0, 0); + default: + case EType::Ortho: + return -1.0 / m_projection_matrix.matrix()(0, 0) - 0.5 * m_projection_matrix.matrix()(0, 0) * m_projection_matrix.matrix()(0, 3); + } +} + +double Camera::get_near_right() const +{ + switch (m_type) + { + case EType::Perspective: + return m_frustrum_zs.first * (m_projection_matrix.matrix()(0, 2) + 1.0) / m_projection_matrix.matrix()(0, 0); + default: + case EType::Ortho: + return 1.0 / m_projection_matrix.matrix()(0, 0) - 0.5 * m_projection_matrix.matrix()(0, 0) * m_projection_matrix.matrix()(0, 3); + } +} + +double Camera::get_near_top() const +{ + switch (m_type) + { + case EType::Perspective: + return m_frustrum_zs.first * (m_projection_matrix.matrix()(1, 2) + 1.0) / m_projection_matrix.matrix()(1, 1); + default: + case EType::Ortho: + return 1.0 / m_projection_matrix.matrix()(1, 1) - 0.5 * m_projection_matrix.matrix()(1, 1) * m_projection_matrix.matrix()(1, 3); + } +} + +double Camera::get_near_bottom() const +{ + switch (m_type) + { + case EType::Perspective: + return m_frustrum_zs.first * (m_projection_matrix.matrix()(1, 2) - 1.0) / m_projection_matrix.matrix()(1, 1); + default: + case EType::Ortho: + return -1.0 / m_projection_matrix.matrix()(1, 1) - 0.5 * m_projection_matrix.matrix()(1, 1) * m_projection_matrix.matrix()(1, 3); + } +} + +double Camera::get_near_width() const +{ + switch (m_type) + { + case EType::Perspective: + return 2.0 * m_frustrum_zs.first / m_projection_matrix.matrix()(0, 0); + default: + case EType::Ortho: + return 2.0 / m_projection_matrix.matrix()(0, 0); + } +} + +double Camera::get_near_height() const +{ + switch (m_type) + { + case EType::Perspective: + return 2.0 * m_frustrum_zs.first / m_projection_matrix.matrix()(1, 1); + default: + case EType::Ortho: + return 2.0 / m_projection_matrix.matrix()(1, 1); + } +} + double Camera::get_fov() const { switch (m_type) @@ -118,17 +190,14 @@ double Camera::get_fov() const }; } -void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) +void Camera::set_viewport(int x, int y, unsigned int w, unsigned int h) { - glsafe(::glViewport(0, 0, w, h)); - glsafe(::glGetIntegerv(GL_VIEWPORT, m_viewport.data())); + m_viewport = { 0, 0, int(w), int(h) }; } -void Camera::apply_view_matrix() +void Camera::apply_viewport() const { - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glLoadIdentity()); - glsafe(::glMultMatrixd(m_view_matrix.data())); + glsafe(::glViewport(m_viewport[0], m_viewport[1], m_viewport[2], m_viewport[3])); } void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) @@ -136,11 +205,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa double w = 0.0; double h = 0.0; - const double old_distance = m_distance; m_frustrum_zs = calc_tight_frustrum_zs_around(box); - if (m_distance != old_distance) - // the camera has been moved re-apply view matrix - apply_view_matrix(); if (near_z > 0.0) m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ); @@ -174,26 +239,36 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa } } - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glLoadIdentity()); + apply_projection(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second); +} + +void Camera::apply_projection(double left, double right, double bottom, double top, double near_z, double far_z) +{ + assert(left != right && bottom != top && near_z != far_z); + const double inv_dx = 1.0 / (right - left); + const double inv_dy = 1.0 / (top - bottom); + const double inv_dz = 1.0 / (far_z - near_z); switch (m_type) { default: case EType::Ortho: { - glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + m_projection_matrix.matrix() << 2.0 * inv_dx, 0.0, 0.0, -(left + right) * inv_dx, + 0.0, 2.0 * inv_dy, 0.0, -(bottom + top) * inv_dy, + 0.0, 0.0, -2.0 * inv_dz, -(near_z + far_z) * inv_dz, + 0.0, 0.0, 0.0, 1.0; break; } case EType::Perspective: { - glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + m_projection_matrix.matrix() << 2.0 * near_z * inv_dx, 0.0, (left + right) * inv_dx, 0.0, + 0.0, 2.0 * near_z * inv_dy, (bottom + top) * inv_dy, 0.0, + 0.0, 0.0, -(near_z + far_z) * inv_dz, -2.0 * near_z * far_z * inv_dz, + 0.0, 0.0, -1.0, 0.0; break; } } - - glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data())); - glsafe(::glMatrixMode(GL_MODELVIEW)); } void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) @@ -351,8 +426,8 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo // box in eye space const BoundingBoxf3 eye_box = box.transformed(m_view_matrix); - near_z = -eye_box.max(2); - far_z = -eye_box.min(2); + near_z = -eye_box.max.z(); + far_z = -eye_box.min.z(); // apply margin near_z -= FrustrumZMargin; @@ -533,19 +608,19 @@ void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up m_distance = (position - target).norm(); const Vec3d new_position = m_target + m_distance * unit_z; - m_view_matrix(0, 0) = unit_x(0); - m_view_matrix(0, 1) = unit_x(1); - m_view_matrix(0, 2) = unit_x(2); + m_view_matrix(0, 0) = unit_x.x(); + m_view_matrix(0, 1) = unit_x.y(); + m_view_matrix(0, 2) = unit_x.z(); m_view_matrix(0, 3) = -unit_x.dot(new_position); - m_view_matrix(1, 0) = unit_y(0); - m_view_matrix(1, 1) = unit_y(1); - m_view_matrix(1, 2) = unit_y(2); + m_view_matrix(1, 0) = unit_y.x(); + m_view_matrix(1, 1) = unit_y.y(); + m_view_matrix(1, 2) = unit_y.z(); m_view_matrix(1, 3) = -unit_y.dot(new_position); - m_view_matrix(2, 0) = unit_z(0); - m_view_matrix(2, 1) = unit_z(1); - m_view_matrix(2, 2) = unit_z(2); + m_view_matrix(2, 0) = unit_z.x(); + m_view_matrix(2, 1) = unit_z.y(); + m_view_matrix(2, 2) = unit_z.z(); m_view_matrix(2, 3) = -unit_z.dot(new_position); m_view_matrix(3, 0) = 0.0; diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 401365ad4e..cfed47884c 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -107,14 +107,23 @@ public: double get_far_z() const { return m_frustrum_zs.second; } const std::pair& get_z_range() const { return m_frustrum_zs; } + double get_near_left() const; + double get_near_right() const; + double get_near_top() const; + double get_near_bottom() const; + double get_near_width() const; + double get_near_height() const; + double get_fov() const; - void apply_viewport(int x, int y, unsigned int w, unsigned int h); - void apply_view_matrix(); + void set_viewport(int x, int y, unsigned int w, unsigned int h); + void apply_viewport() const; // Calculates and applies the projection matrix tighting the frustrum z range around the given box. // If larger z span is needed, pass the desired values of near and far z (negative values are ignored) void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0); + void apply_projection(double left, double right, double bottom, double top, double near_z, double far_z); + void zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor); void zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor = DefaultZoomToVolumesMarginFactor); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 934ddb1019..b26e3e8ca6 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -511,7 +511,7 @@ void ConfigManipulation::apply_null_fff_config(DynamicPrintConfig *config, std:: void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, const bool is_global_config) { PresetBundle *preset_bundle = wxGetApp().preset_bundle; - + auto gcflavor = preset_bundle->printers.get_edited_preset().config.option>("gcode_flavor")->value; bool have_volumetric_extrusion_rate_slope = config->option("max_volumetric_extrusion_rate_slope")->value > 0; @@ -520,27 +520,27 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_line("max_volumetric_extrusion_rate_slope_segment_length", have_volumetric_extrusion_rate_slope); if(have_volumetric_extrusion_rate_slope) config->set_key_value("enable_arc_fitting", new ConfigOptionBool(false)); if(have_volumetric_extrusion_rate_slope_segment_length==0) { - DynamicPrintConfig new_conf = *config; + DynamicPrintConfig new_conf = *config; new_conf.set_key_value("max_volumetric_extrusion_rate_slope_segment_length", new ConfigOptionInt(1)); - apply(config, &new_conf); + apply(config, &new_conf); } bool have_perimeters = config->opt_int("wall_loops") > 0; for (auto el : { "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "detect_thin_wall", "detect_overhang_wall", - "seam_position", "staggered_inner_seams", "wall_infill_order", "outer_wall_line_width", - "inner_wall_speed", "outer_wall_speed", "small_perimeter_speed", "small_perimeter_threshold" }) + "seam_position", "staggered_inner_seams", "wall_infill_order", "outer_wall_line_width", + "inner_wall_speed", "outer_wall_speed", "small_perimeter_speed", "small_perimeter_threshold" }) toggle_field(el, have_perimeters); - + bool have_infill = config->option("sparse_infill_density")->value > 0; // sparse_infill_filament uses the same logic as in Print::extruders() for (auto el : { "sparse_infill_pattern", "infill_combination", - "minimum_sparse_infill_area", "sparse_infill_filament", "infill_anchor_max"}) + "minimum_sparse_infill_area", "sparse_infill_filament", "infill_anchor_max"}) toggle_line(el, have_infill); // Only allow configuration of open anchors if the anchoring is enabled. bool has_infill_anchors = have_infill && config->option("infill_anchor_max")->value > 0; toggle_field("infill_anchor", has_infill_anchors); - + bool has_spiral_vase = config->opt_bool("spiral_mode"); bool has_top_solid_infill = config->opt_int("top_shell_layers") > 0; bool has_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0; @@ -548,43 +548,43 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co // solid_infill_filament uses the same logic as in Print::extruders() for (auto el : { "top_surface_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "solid_infill_filament"}) toggle_field(el, has_solid_infill); - + for (auto el : { "infill_direction", "sparse_infill_line_width", - "sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle" }) + "sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle" }) toggle_field(el, have_infill || has_solid_infill); - + toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_solid_infill); toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_solid_infill); - + // Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476). toggle_field("gap_infill_speed", have_perimeters); - + for (auto el : { "top_surface_line_width", "top_surface_speed" }) toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill)); - + bool have_default_acceleration = config->opt_float("default_acceleration") > 0; - + for (auto el : {"outer_wall_acceleration", "inner_wall_acceleration", "initial_layer_acceleration", - "top_surface_acceleration", "travel_acceleration", "bridge_acceleration", "sparse_infill_acceleration", "internal_solid_infill_acceleration"}) + "top_surface_acceleration", "travel_acceleration", "bridge_acceleration", "sparse_infill_acceleration", "internal_solid_infill_acceleration"}) toggle_field(el, have_default_acceleration); - + bool have_default_jerk = config->opt_float("default_jerk") > 0; - + for (auto el : { "outer_wall_jerk", "inner_wall_jerk", "initial_layer_jerk", "top_surface_jerk","travel_jerk", "infill_jerk"}) toggle_field(el, have_default_jerk); - + bool have_skirt = config->opt_int("skirt_loops") > 0; toggle_field("skirt_height", have_skirt && config->opt_enum("draft_shield") != dsEnabled); for (auto el : { "skirt_distance", "draft_shield"}) toggle_field(el, have_skirt); - + bool have_brim = (config->opt_enum("brim_type") != btNoBrim); toggle_field("brim_object_gap", have_brim); bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim; toggle_field("brim_width", have_brim_width); // wall_filament uses the same logic as in Print::extruders() toggle_field("wall_filament", have_perimeters || have_brim); - + bool have_brim_ear = (config->opt_enum("brim_type") == btEar); const auto brim_width = config->opt_float("brim_width"); // disable brim_ears_max_angle and brim_ears_detection_length if brim_width is 0 @@ -593,32 +593,32 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co // hide brim_ears_max_angle and brim_ears_detection_length if brim_ear is not selected toggle_line("brim_ears_max_angle", have_brim_ear); toggle_line("brim_ears_detection_length", have_brim_ear); - + // Hide Elephant foot compensation layers if elefant_foot_compensation is not enabled toggle_line("elefant_foot_compensation_layers", config->opt_float("elefant_foot_compensation") > 0); - + bool have_raft = config->opt_int("raft_layers") > 0; bool have_support_material = config->opt_bool("enable_support") || have_raft; - + SupportType support_type = config->opt_enum("support_type"); bool have_support_interface = config->opt_int("support_interface_top_layers") > 0 || config->opt_int("support_interface_bottom_layers") > 0; bool have_support_soluble = have_support_material && config->opt_float("support_top_z_distance") == 0; auto support_style = config->opt_enum("support_style"); for (auto el : { "support_style", "support_base_pattern", - "support_base_pattern_spacing", "support_expansion", "support_angle", - "support_interface_pattern", "support_interface_top_layers", "support_interface_bottom_layers", - "bridge_no_support", "max_bridge_length", "support_top_z_distance", "support_bottom_z_distance", - //BBS: add more support params to dependent of enable_support - "support_type", "support_on_build_plate_only", "support_critical_regions_only", - "support_object_xy_distance"/*, "independent_support_layer_height"*/}) + "support_base_pattern_spacing", "support_expansion", "support_angle", + "support_interface_pattern", "support_interface_top_layers", "support_interface_bottom_layers", + "bridge_no_support", "max_bridge_length", "support_top_z_distance", "support_bottom_z_distance", + //BBS: add more support params to dependent of enable_support + "support_type", "support_on_build_plate_only", "support_critical_regions_only", + "support_object_xy_distance"/*, "independent_support_layer_height"*/}) toggle_field(el, have_support_material); toggle_field("support_threshold_angle", have_support_material && is_auto(support_type)); //toggle_field("support_closing_radius", have_support_material && support_style == smsSnug); - + bool support_is_tree = config->opt_bool("enable_support") && is_tree(support_type); bool support_is_normal_tree = support_is_tree && support_style != smsOrganic && - // Orca: use organic as default - support_style != smsDefault; + // Orca: use organic as default + support_style != smsDefault; bool support_is_organic = support_is_tree && !support_is_normal_tree; // settings shared by normal and organic trees for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter" }) @@ -629,85 +629,85 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co // settings specific to organic trees for (auto el : {"tree_support_branch_angle_organic", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic","tree_support_angle_slow","tree_support_tip_diameter", "tree_support_top_rate", "tree_support_branch_diameter_angle", "tree_support_branch_diameter_double_wall"}) toggle_line(el, support_is_organic); - + toggle_field("tree_support_brim_width", support_is_tree && !config->opt_bool("tree_support_auto_brim")); // non-organic tree support use max_bridge_length instead of bridge_no_support toggle_line("max_bridge_length", support_is_normal_tree); toggle_line("bridge_no_support", !support_is_normal_tree); - + // This is only supported for auto normal tree toggle_line("support_critical_regions_only", is_auto(support_type) && support_is_normal_tree); - + for (auto el : { "support_interface_spacing", "support_interface_filament", - "support_interface_loop_pattern", "support_bottom_interface_spacing" }) + "support_interface_loop_pattern", "support_bottom_interface_spacing" }) toggle_field(el, have_support_material && have_support_interface); - + bool have_skirt_height = have_skirt && - (config->opt_int("skirt_height") > 1 || config->opt_enum("draft_shield") != dsEnabled); + (config->opt_int("skirt_height") > 1 || config->opt_enum("draft_shield") != dsEnabled); toggle_line("support_speed", have_support_material || have_skirt_height); toggle_line("support_interface_speed", have_support_material && have_support_interface); - + // BBS //toggle_field("support_material_synchronize_layers", have_support_soluble); - + toggle_field("inner_wall_line_width", have_perimeters || have_skirt || have_brim); toggle_field("support_filament", have_support_material || have_skirt); - + toggle_line("raft_contact_distance", have_raft && !have_support_soluble); - + // Orca: Raft, grid, snug and organic supports use these two parameters to control the size & density of the "brim"/flange for (auto el : { "raft_first_layer_expansion", "raft_first_layer_density"}) toggle_field(el, have_support_material && !(support_is_normal_tree && !have_raft)); - + bool has_ironing = (config->opt_enum("ironing_type") != IroningType::NoIroning); for (auto el : { "ironing_pattern", "ironing_flow", "ironing_spacing", "ironing_speed", "ironing_angle" }) toggle_line(el, has_ironing); - + // bool have_sequential_printing = (config->opt_enum("print_sequence") == PrintSequence::ByObject); // for (auto el : { "extruder_clearance_radius", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" }) // toggle_field(el, have_sequential_printing); - + bool have_ooze_prevention = config->opt_bool("ooze_prevention"); toggle_field("standby_temperature_delta", have_ooze_prevention); - + bool have_prime_tower = config->opt_bool("enable_prime_tower"); for (auto el : { "prime_tower_width", "prime_tower_brim_width"}) toggle_line(el, have_prime_tower); - + bool purge_in_primetower = preset_bundle->printers.get_edited_preset().config.opt_bool("purge_in_prime_tower"); - + for (auto el : {"wipe_tower_rotation_angle", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers"}) toggle_line(el, have_prime_tower && purge_in_primetower); - + toggle_line("prime_volume",have_prime_tower && !purge_in_primetower); - + for (auto el : {"flush_into_infill", "flush_into_support", "flush_into_objects"}) toggle_field(el, have_prime_tower); - - // BBS: MusangKing - Hide "Independent support layer height" option + + // BBS: MusangKing - Hide "Independent support layer height" option toggle_line("independent_support_layer_height", have_support_material && !have_prime_tower); - + bool have_avoid_crossing_perimeters = config->opt_bool("reduce_crossing_wall"); toggle_line("max_travel_detour_distance", have_avoid_crossing_perimeters); - + bool has_overhang_speed = config->opt_bool("enable_overhang_speed"); for (auto el : {"overhang_speed_classic", "overhang_1_4_speed", - "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed"}) + "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed"}) toggle_line(el, has_overhang_speed); bool has_overhang_speed_classic = config->opt_bool("overhang_speed_classic"); toggle_line("slowdown_for_curled_perimeters",!has_overhang_speed_classic && has_overhang_speed); - + toggle_line("flush_into_objects", !is_global_config); - + bool has_fuzzy_skin = (config->opt_enum("fuzzy_skin") != FuzzySkinType::None); for (auto el : { "fuzzy_skin_thickness", "fuzzy_skin_point_distance"}) toggle_line(el, has_fuzzy_skin); - + bool have_arachne = config->opt_enum("wall_generator") == PerimeterGeneratorType::Arachne; for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "min_feature_size", "min_bead_width", "wall_distribution_count", "initial_layer_min_bead_width"}) + "min_feature_size", "min_bead_width", "wall_distribution_count", "initial_layer_min_bead_width"}) toggle_line(el, have_arachne); toggle_field("detect_thin_wall", !have_arachne); @@ -719,23 +719,30 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_line(el, gcflavor == gcfKlipper); if(gcflavor == gcfKlipper) toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable")); - + bool have_make_overhang_printable = config->opt_bool("make_overhang_printable"); toggle_line("make_overhang_printable_angle", have_make_overhang_printable); toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable); - + toggle_line("exclude_object", gcflavor == gcfKlipper); - + toggle_line("min_width_top_surface",config->opt_bool("only_one_wall_top")); - + for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted" }) toggle_line(el, config->opt_bool("hole_to_polyhole")); - + bool has_detect_overhang_wall = config->opt_bool("detect_overhang_wall"); bool has_overhang_reverse = config->opt_bool("overhang_reverse"); bool allow_overhang_reverse = has_detect_overhang_wall && !has_spiral_vase; toggle_field("overhang_reverse", allow_overhang_reverse); toggle_line("overhang_reverse_threshold", allow_overhang_reverse && has_overhang_reverse); + toggle_line("overhang_reverse_internal_only", allow_overhang_reverse && has_overhang_reverse); + bool has_overhang_reverse_internal_only = config->opt_bool("overhang_reverse_internal_only"); + if (has_overhang_reverse_internal_only){ + DynamicPrintConfig new_conf = *config; + new_conf.set_key_value("overhang_reverse_threshold", new ConfigOptionFloatOrPercent(0,true)); + apply(config, &new_conf); + } toggle_line("timelapse_type", is_BBL_Printer); } diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 572fb829f5..a34a7cd46e 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -36,6 +36,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/libslic3r.h" #include "libslic3r/Model.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -777,9 +778,9 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector* are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); wxString text; if (all_printers) { diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 091a60b45f..3565a40b95 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1619,8 +1619,7 @@ boost::any& ColourPicker::get_value() if (colour == wxTransparentColour) m_value = std::string(""); else { - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - m_value = clr_str.ToStdString(); + m_value = encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue())); } return m_value; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 99670ee68c..5382ce746b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -90,42 +90,6 @@ static EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(EMoveType::Retract) + id); } -static std::array decode_color(const std::string& color) { - static const float INV_255 = 1.0f / 255.0f; - - std::array ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - ret[j] = float(digit1 * 16 + digit2) * INV_255; - } - } - else if (color.size() == 9 && color.front() == '#') { - for (size_t j = 0; j < 4; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - ret[j] = float(digit1 * 16 + digit2) * INV_255; - } - } - return ret; -} - -static std::vector> decode_colors(const std::vector& colors) { - std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - output[i] = decode_color(colors[i]); - } - return output; -} - // Round to a bin with minimum two digits resolution. // Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. static float round_to_bin(const float value) @@ -263,7 +227,7 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move move.volumetric_rate(), move.layer_duration, move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); } -GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const +ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const { // Input value scaled to the colors range const float step = step_size(); @@ -280,15 +244,8 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); - // Compute how far the value is between the low and high colors so that they can be interpolated - const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); - // Interpolate between the low and high colors to find exactly which color the input value should get - Color ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - for (unsigned int i = 0; i < 3; ++i) { - ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); - } - return ret; + return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast(color_low_idx)); } float GCodeViewer::Extrusions::Range::step_size() const { @@ -331,7 +288,7 @@ void GCodeViewer::SequentialView::Marker::init(std::string filename) } else { m_model.init_from_file(filename); } - m_model.set_color(-1, {1.0f, 1.0f, 1.0f, 0.5f}); + m_model.set_color({ 1.0f, 1.0f, 1.0f, 0.5f }); } void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) @@ -345,7 +302,7 @@ void GCodeViewer::SequentialView::Marker::update_curr_move(const GCodeProcessorR } //BBS: GUI refactor: add canvas size from parameters -void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_height, const EViewType& view_type) const +void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_height, const EViewType& view_type) { if (!m_visible) return; @@ -360,13 +317,16 @@ void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_he shader->start_using(); shader->set_uniform("emission_factor", 0.0f); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixf(m_world_transform.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d model_matrix = m_world_transform.cast(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); m_model.render(); - glsafe(::glPopMatrix()); - shader->stop_using(); glsafe(::glDisable(GL_BLEND)); @@ -584,11 +544,11 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, f return ret; }; - static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; + static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; - static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; - static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; - static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; + static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; + static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; + static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; if (!m_visible || !wxGetApp().show_gcode_window() || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0) return; @@ -713,7 +673,7 @@ void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished mapping file " << m_filename; } } -void GCodeViewer::SequentialView::render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) const +void GCodeViewer::SequentialView::render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) { if (has_render_path) marker.render(canvas_width, canvas_height, view_type); @@ -728,7 +688,7 @@ if (has_render_path) gcode_window.render(legend_height + 2, std::max(10.f, (float)canvas_height - 40), (float)canvas_width - (float)right_margin, static_cast(gcode_ids[current.last])); } -const std::vector GCodeViewer::Extrusion_Role_Colors {{ +const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.90f, 0.70f, 0.70f, 1.0f }, // erNone { 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter { 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter @@ -750,7 +710,7 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.37f, 0.82f, 0.58f, 1.0f } // erCustom }}; -const std::vector GCodeViewer::Options_Colors {{ +const std::vector GCodeViewer::Options_Colors{ { { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams @@ -760,7 +720,7 @@ const std::vector GCodeViewer::Options_Colors {{ { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes }}; -const std::vector GCodeViewer::Travel_Colors {{ +const std::vector GCodeViewer::Travel_Colors{ { { 0.219f, 0.282f, 0.609f, 1.0f }, // Move { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude { 0.505f, 0.064f, 0.028f, 1.0f } // Retract @@ -768,7 +728,7 @@ const std::vector GCodeViewer::Travel_Colors {{ // Normal ranges // blue to red -const std::vector GCodeViewer::Range_Colors{{ +const std::vector GCodeViewer::Range_Colors{ { decode_color_to_float_array("#0b2c7a"), // bluish decode_color_to_float_array("#135985"), decode_color_to_float_array("#1c8891"), @@ -782,8 +742,8 @@ const std::vector GCodeViewer::Range_Colors{{ decode_color_to_float_array("#942616") // reddish }}; -const GCodeViewer::Color GCodeViewer::Wipe_Color = { 1.0f, 1.0f, 0.0f, 1.0f }; -const GCodeViewer::Color GCodeViewer::Neutral_Color = { 0.25f, 0.25f, 0.25f, 1.0f }; +const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW(); +const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); GCodeViewer::GCodeViewer() { @@ -858,8 +818,8 @@ void GCodeViewer::init(ConfigOptionMode mode, PresetBundle* preset_bundle) } case EMoveType::Travel: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; - buffer.shader = "toolpaths_lines"; + buffer.vertices.format = VBuffer::EFormat::Position; + buffer.shader = "flat"; break; } } @@ -963,7 +923,7 @@ std::vector GCodeViewer::get_plater_extruder() //BBS: always load shell at preview void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, const BuildVolume& build_volume, - const std::vector& exclude_bounding_box, bool initialized, ConfigOptionMode mode, bool only_gcode) + const std::vector& exclude_bounding_box, ConfigOptionMode mode, bool only_gcode) { // avoid processing if called with the same gcode_result if (m_last_result_id == gcode_result.id) { @@ -1018,7 +978,7 @@ m_sequential_view.m_show_gcode_window = false; //BBS: always load shell at preview /*if (wxGetApp().is_editor()) { - //load_shells(print, initialized); + load_shells(print); } else {*/ //BBS: add only gcode mode @@ -1140,13 +1100,13 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) { // update tool colors from config stored in the gcode - m_tools.m_tool_colors = decode_colors(gcode_result.extruder_colors); + decode_colors(gcode_result.extruder_colors, m_tools.m_tool_colors); m_tools.m_tool_visibles = std::vector(m_tools.m_tool_colors.size()); for (auto item: m_tools.m_tool_visibles) item = true; } else { // update tool colors - m_tools.m_tool_colors = decode_colors(str_tool_colors); + decode_colors(str_tool_colors, m_tools.m_tool_colors); m_tools.m_tool_visibles = std::vector(m_tools.m_tool_colors.size()); for (auto item : m_tools.m_tool_visibles) item = true; } @@ -1154,9 +1114,11 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v for (int i = 0; i < m_tools.m_tool_colors.size(); i++) { m_tools.m_tool_colors[i] = adjust_color_for_rendering(m_tools.m_tool_colors[i]); } - // ensure there are enough colors defined + ColorRGBA default_color; + decode_color("#FF8000", default_color); + // ensure there are enough colors defined while (m_tools.m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) { - m_tools.m_tool_colors.push_back(decode_color("#FF8000")); + m_tools.m_tool_colors.push_back(default_color); m_tools.m_tool_visibles.push_back(true); } @@ -1247,7 +1209,7 @@ void GCodeViewer::reset() m_paths_bounding_box = BoundingBoxf3(); m_max_bounding_box = BoundingBoxf3(); m_max_print_height = 0.0f; - m_tools.m_tool_colors = std::vector(); + m_tools.m_tool_colors = std::vector(); m_tools.m_tool_visibles = std::vector(); m_extruders_count = 0; m_extruder_ids = std::vector(); @@ -1276,14 +1238,11 @@ void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin) m_statistics.total_instances_gpu_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS - render_shells(); - // Orca: add shell overlay effect - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - if (m_roles.empty()) return; - glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_DEPTH_TEST)); + render_shells(); render_toolpaths(); float legend_height = 0.0f; render_legend(legend_height, canvas_width, canvas_height, right_margin); @@ -1346,12 +1305,12 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai #if 1 Camera camera; - camera.apply_viewport(0,0,thumbnail_data.width, thumbnail_data.height); + camera.set_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.apply_viewport(); camera.set_scene_box(plate_box); camera.set_type(Camera::EType::Ortho); camera.set_target(center); camera.select_view("top"); - camera.apply_view_matrix(); camera.zoom_to_box(plate_box, 1.0f); camera.apply_projection(plate_box); @@ -1365,7 +1324,7 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(!path.sizes.empty()); assert(!path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_triangles_calls_count; @@ -1387,7 +1346,7 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai } if (range.vbo > 0) { - buffer.model.model.set_color(-1, range.color); + buffer.model.model.set_color(range.color); buffer.model.model.render_instanced(range.vbo, range.count); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_instanced_models_calls_count; @@ -1400,7 +1359,7 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai #if ENABLE_GCODE_VIEWER_STATISTICS auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { #else - auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { #endif // ENABLE_GCODE_VIEWER_STATISTICS struct Range @@ -1416,12 +1375,16 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai const IBuffer& i_buffer = buffer.indices[j]; buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); @@ -1444,11 +1407,11 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); buffer_range.first = buffer_range.last; @@ -1464,10 +1427,15 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai if (!buffer.visible || !buffer.has_data()) continue; - GLShaderProgram* shader = opengl_manager.get_shader("cali"); + GLShaderProgram* shader = opengl_manager.get_shader("flat"); if (shader != nullptr) { shader->start_using(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + int position_id = shader->get_attrib_location("v_position"); + int normal_id = shader->get_attrib_location("v_normal"); + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { //shader->set_uniform("emission_factor", 0.25f); render_as_instanced_model(buffer, *shader); @@ -1475,7 +1443,7 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai } else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { //shader->set_uniform("emission_factor", 0.25f); - render_as_batched_model(buffer, *shader); + render_as_batched_model(buffer, *shader, position_id, normal_id); //shader->set_uniform("emission_factor", 0.0f); } else { @@ -1493,12 +1461,16 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai continue; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } bool has_normals = false;// buffer.vertices.normal_size_floats() > 0; if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); @@ -1514,11 +1486,12 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } } @@ -1904,7 +1877,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const return; // collect color information to generate materials - std::vector colors; + std::vector colors; for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } @@ -1926,10 +1899,10 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SoftFever_VERSION); unsigned int colors_count = 1; - for (const Color& color : colors) { + for (const ColorRGBA& color : colors) { fprintf(fp, "\nnewmtl material_%d\n", colors_count++); fprintf(fp, "Ka 1 1 1\n"); - fprintf(fp, "Kd %g %g %g\n", color[0], color[1], color[2]); + fprintf(fp, "Kd %g %g %g\n", color.r(), color.g(), color.b()); fprintf(fp, "Ks 0 0 0\n"); } @@ -1990,7 +1963,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const } size_t i = 0; - for (const Color& color : colors) { + for (const ColorRGBA& color : colors) { // save material triangles to file fprintf(fp, "\nusemtl material_%zu\n", i + 1); fprintf(fp, "# triangles material %zu\n", i + 1); @@ -2059,29 +2032,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const log_memory_used(label, vertices_size + indices_size); }; - // format data into the buffers to be rendered as points - auto add_vertices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { - vertices.push_back(curr.position.x()); - vertices.push_back(curr.position.y()); - vertices.push_back(curr.position.z()); - }; - auto add_indices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id); - indices.push_back(static_cast(indices.size())); - }; - // format data into the buffers to be rendered as lines auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { - auto add_vertex = [&vertices](const Vec3f& position, const Vec3f& normal) { + auto add_vertex = [&vertices](const Vec3f& position) { // add position vertices.push_back(position.x()); vertices.push_back(position.y()); vertices.push_back(position.z()); - // add normal - vertices.push_back(normal.x()); - vertices.push_back(normal.y()); - vertices.push_back(normal.z()); }; // x component of the normal to the current segment (the normal is parallel to the XY plane) //BBS: Has modified a lot for this function to support arc move @@ -2089,13 +2046,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const for (size_t i = 0; i < loop_num + 1; i++) { const Vec3f &previous = (i == 0? prev.position : curr.interpolation_points[i-1]); const Vec3f ¤t = (i == loop_num? curr.position : curr.interpolation_points[i]); - const Vec3f dir = (current - previous).normalized(); - Vec3f normal(dir.y(), -dir.x(), 0.0); - normal.normalize(); // add previous vertex - add_vertex(previous, normal); + add_vertex(previous); // add current vertex - add_vertex(current, normal); + add_vertex(current); } }; //BBS: modify a lot to support arc travel @@ -2332,28 +2286,27 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const }; // format data into the buffers to be rendered as batched model - auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { const double width = static_cast(1.5f * curr.width); const double height = static_cast(1.5f * curr.height); const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); - for (const auto& entity : data.entities) { - // append vertices - for (size_t i = 0; i < entity.positions.size(); ++i) { - // append position - const Vec3d position = trafo * entity.positions[i].cast(); - vertices.push_back(static_cast(position.x())); - vertices.push_back(static_cast(position.y())); - vertices.push_back(static_cast(position.z())); + // append vertices + const size_t vertices_count = data.vertices_count(); + for (size_t i = 0; i < vertices_count; ++i) { + // append position + const Vec3d position = trafo * data.extract_position_3(i).cast(); + vertices.push_back(float(position.x())); + vertices.push_back(float(position.y())); + vertices.push_back(float(position.z())); - // append normal - const Vec3d normal = normal_matrix * entity.normals[i].cast(); - vertices.push_back(static_cast(normal.x())); - vertices.push_back(static_cast(normal.y())); - vertices.push_back(static_cast(normal.z())); - } + // append normal + const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast(); + vertices.push_back(float(normal.x())); + vertices.push_back(float(normal.y())); + vertices.push_back(float(normal.z())); } // append instance position @@ -2364,11 +2317,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const instances_ids.push_back(move_id); }; - auto add_indices_as_model_batch = [](const GLModel::InitializationData& data, IndexBuffer& indices, IBufferType base_index) { - for (const auto& entity : data.entities) { - for (size_t i = 0; i < entity.indices.size(); ++i) { - indices.push_back(static_cast(entity.indices[i] + base_index)); - } + auto add_indices_as_model_batch = [](const GLModel::Geometry& data, IndexBuffer& indices, IBufferType base_index) { + const size_t indices_count = data.indices_count(); + for (size_t i = 0; i < indices_count; ++i) { + indices.push_back(static_cast(data.extract_index(i) + base_index)); } }; @@ -2543,7 +2495,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const switch (t_buffer.render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; } case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id); break; } case TBuffer::ERenderPrimitiveType::InstancedModel: @@ -2931,8 +2882,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) { i_multibuffer.push_back(IndexBuffer()); vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && - t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { Path& last_path = t_buffer.paths.back(); last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); } @@ -2949,8 +2899,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const curr_vertex_buffer.second = 0; vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && - t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { Path& last_path = t_buffer.paths.back(); last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); } @@ -2960,11 +2909,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const switch (t_buffer.render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: { - add_indices_as_point(curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); - curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); - break; - } case TBuffer::ERenderPrimitiveType::Line: { add_indices_as_line(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); break; @@ -3131,9 +3075,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const } //BBS: always load shell when preview -void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing) +void GCodeViewer::load_shells(const Print& print, bool force_previewing) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": force_previewing=%1%") %force_previewing; if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) { //BBS: update force previewing logic if (force_previewing) @@ -3181,7 +3125,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p instance_ids.resize(instance_index); size_t current_volumes_count = m_shells.volumes.volumes.size(); - m_shells.volumes.load_object(model_obj, object_idx, instance_ids, "object", initialized); + m_shells.volumes.load_object(model_obj, object_idx, instance_ids); // adjust shells' z if raft is present const SlicingParameters& slicing_parameters = obj->slicing_parameters(); @@ -3231,7 +3175,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p for (GLVolume* volume : m_shells.volumes.volumes) { volume->zoom_to_volumes = false; - volume->color[3] = 0.5f; + volume->color.a(0.5f); volume->force_native_color = true; volume->set_render_color(); //BBS: add shell bounding box logic @@ -3254,7 +3198,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": enter, m_buffers size %1%!")%m_buffers.size(); auto extrusion_color = [this](const Path& path) { - Color color; + ColorRGBA color; switch (m_view_type) { case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } @@ -3269,7 +3213,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Tool: { color = m_tools.m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { if (path.cp_color_id >= static_cast(m_tools.m_tool_colors.size())) - color = { 0.5f, 0.5f, 0.5f, 1.0f }; + color = ColorRGBA::GRAY(); else { color = m_tools.m_tool_colors[path.cp_color_id]; color = adjust_color_for_rendering(color); @@ -3282,7 +3226,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool color = {id, role, id, 1.0f}; break; } - default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; } + default: { color = ColorRGBA::WHITE(); break; } } return color; @@ -3515,7 +3459,7 @@ m_no_render_path = false; if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) continue; - Color color; + ColorRGBA color; switch (path.type) { case EMoveType::Tool_change: @@ -3560,10 +3504,6 @@ m_no_render_path = false; unsigned int size_in_indices = 0; switch (buffer.render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: { - size_in_indices = buffer.indices_per_segment(); - break; - } case TBuffer::ERenderPrimitiveType::Line: case TBuffer::ERenderPrimitiveType::Triangle: { // BBS: modify to support moves which has internal point @@ -3807,69 +3747,20 @@ m_no_render_path = false; void GCodeViewer::render_toolpaths() { -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - float point_size = 20.0f; -#else - float point_size = 0.8f; -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; const Camera& camera = wxGetApp().plater()->get_camera(); - double zoom = camera.get_zoom(); - const std::array& viewport = camera.get_viewport(); - float near_plane_height = camera.get_type() == Camera::EType::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : - static_cast(viewport[3]) * 0.0005; + const double zoom = camera.get_zoom(); - auto shader_init_as_points = [zoom, point_size, near_plane_height](GLShaderProgram& shader) { -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("use_fixed_screen_size", 1); -#else - shader.set_uniform("use_fixed_screen_size", 0); -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("zoom", zoom); - shader.set_uniform("percent_outline_radius", 0.0f); - shader.set_uniform("percent_center_radius", 0.33f); - shader.set_uniform("point_size", point_size); - shader.set_uniform("near_plane_height", near_plane_height); - }; - - auto render_as_points = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::reverse_iterator it_path, std::vector::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) { - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glEnable(GL_POINT_SPRITE)); - - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - - glsafe(::glDisable(GL_POINT_SPRITE)); - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - }; - - auto shader_init_as_lines = [light_intensity](GLShaderProgram &shader) { - shader.set_uniform("light_intensity", light_intensity); - }; auto render_as_lines = [ #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::reverse_iterator it_path, std::vector::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) { + ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { const RenderPath& path = *it; // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_lines_calls_count; @@ -3881,13 +3772,13 @@ void GCodeViewer::render_toolpaths() #if ENABLE_GCODE_VIEWER_STATISTICS this #endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::reverse_iterator it_path, std::vector::reverse_iterator it_end, GLShaderProgram& shader, int uniform_color) { + ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { const RenderPath& path = *it; // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. assert(! path.sizes.empty()); assert(! path.offsets.empty()); - glsafe(::glUniform4fv(uniform_color, 1, static_cast(path.color.data()))); + shader.set_uniform(uniform_color, path.color); glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_triangles_calls_count; @@ -3909,7 +3800,7 @@ void GCodeViewer::render_toolpaths() } if (range.vbo > 0) { - buffer.model.model.set_color(-1, range.color); + buffer.model.model.set_color(range.color); buffer.model.model.render_instanced(range.vbo, range.count); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_instanced_models_calls_count; @@ -3920,9 +3811,9 @@ void GCodeViewer::render_toolpaths() }; #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { #else - auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { #endif // ENABLE_GCODE_VIEWER_STATISTICS struct Range @@ -3932,30 +3823,34 @@ void GCodeViewer::render_toolpaths() bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } }; Range buffer_range = { 0, 0 }; - size_t indices_per_instance = buffer.model.data.indices_count(); + const size_t indices_per_instance = buffer.model.data.indices_count(); for (size_t j = 0; j < buffer.indices.size(); ++j) { const IBuffer& i_buffer = buffer.indices[j]; buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } + const bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); for (auto& range : buffer.model.instances.render_ranges.ranges) { - Range range_range = { range.offset, range.offset + range.count }; + const Range range_range = { range.offset, range.offset + range.count }; if (range_range.intersects(buffer_range)) { shader.set_uniform("uniform_color", range.color); - unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; - size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); - Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; - size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; + const unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; + const size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); + const Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; + const size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; if (count > 0) { glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -3967,10 +3862,10 @@ void GCodeViewer::render_toolpaths() glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); buffer_range.first = buffer_range.last; @@ -3981,9 +3876,8 @@ void GCodeViewer::render_toolpaths() return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); }; - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Count); - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":begin_id %1%, end_id %2% ")%(int)begin_id %(int)end_id; + const unsigned char begin_id = buffer_id(EMoveType::Retract); + const unsigned char end_id = buffer_id(EMoveType::Count); for (unsigned char i = begin_id; i < end_id; ++i) { TBuffer& buffer = m_buffers[i]; @@ -3991,119 +3885,135 @@ void GCodeViewer::render_toolpaths() continue; GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); - if (shader != nullptr) { - shader->start_using(); + if (shader == nullptr) + continue; - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { - shader->set_uniform("emission_factor", 0.25f); - render_as_instanced_model(buffer, *shader); - shader->set_uniform("emission_factor", 0.0f); - } - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - shader->set_uniform("emission_factor", 0.25f); - render_as_batched_model(buffer, *shader); - shader->set_uniform("emission_factor", 0.0f); - } - else { - switch (buffer.render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: shader_init_as_points(*shader); break; - case TBuffer::ERenderPrimitiveType::Line: shader_init_as_lines(*shader); break; - default: break; - } - int uniform_color = shader->get_uniform_location("uniform_color"); - auto it_path = buffer.render_paths.rbegin(); - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":buffer indices size %1%, render_path size %2% ")%buffer.indices.size() %buffer.render_paths.size(); - unsigned int indices_count = static_cast(buffer.indices.size()); - for (unsigned int index = 0; index < indices_count; ++index) { - unsigned int ibuffer_id = indices_count - index - 1; - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - // Skip all paths with ibuffer_id < ibuffer_id. - for (; it_path != buffer.render_paths.rend() && it_path->ibuffer_id > ibuffer_id; ++ it_path) ; - if (it_path == buffer.render_paths.rend() || it_path->ibuffer_id < ibuffer_id) - // Not found. This shall not happen. - continue; + shader->start_using(); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - } + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - - // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - render_as_points(it_path, buffer.render_paths.rend(), *shader, uniform_color); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - glsafe(::glLineWidth(static_cast(line_width(zoom)))); - render_as_lines(it_path, buffer.render_paths.rend(), *shader, uniform_color); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - render_as_triangles(it_path, buffer.render_paths.rend(), *shader, uniform_color); - break; - } - default: { break; } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - } - - shader->stop_using(); + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { + shader->set_uniform("emission_factor", 0.25f); + render_as_instanced_model(buffer, *shader); + shader->set_uniform("emission_factor", 0.0f); } + else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { + shader->set_uniform("emission_factor", 0.25f); + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); + render_as_batched_model(buffer, *shader, position_id, normal_id); + shader->set_uniform("emission_factor", 0.0f); + } + else { + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); + const int uniform_color = shader->get_uniform_location("uniform_color"); + + auto it_path = buffer.render_paths.begin(); + for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast(buffer.indices.size()); ++ibuffer_id) { + const IBuffer& i_buffer = buffer.indices[ibuffer_id]; + // Skip all paths with ibuffer_id < ibuffer_id. + for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++it_path); + if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id) + // Not found. This shall not happen. + continue; + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } + const bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (has_normals) { + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + + // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Line: { + glsafe(::glLineWidth(static_cast(line_width(zoom)))); + render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: { + render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color); + break; + } + default: { break; } + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + } + + shader->stop_using(); } #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_sequential_range_cap = [this] + auto render_sequential_range_cap = [this, &camera] #else - auto render_sequential_range_cap = [] + auto render_sequential_range_cap = [&camera] #endif // ENABLE_GCODE_VIEWER_STATISTICS (const SequentialRangeCap& cap) { - GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); - if (shader != nullptr) { - shader->start_using(); + const TBuffer* buffer = cap.buffer; + GLShaderProgram* shader = wxGetApp().get_shader(buffer->shader.c_str()); + if (shader == nullptr) + return; - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); - glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = cap.buffer->vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + shader->start_using(); + + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); + + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer->vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } + const bool has_normals = buffer->vertices.normal_size_floats() > 0; + if (has_normals) { + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer->vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); } + } - shader->set_uniform("uniform_color", cap.color); + shader->set_uniform("uniform_color", cap.color); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - shader->stop_using(); - } + shader->stop_using(); }; for (unsigned int i = 0; i < 2; ++i) { @@ -4123,23 +4033,16 @@ void GCodeViewer::render_shells() if (shader == nullptr) return; - // when the background processing is enabled, it may happen that the shells data have been loaded - // before opengl has been initialized for the preview canvas. - // when this happens, the volumes' data have not been sent to gpu yet. - for (GLVolume* v : m_shells.volumes.volumes) { - if (!v->indexed_vertex_array.has_VBOs()) - v->finalize_geometry(true); - } - - glsafe(::glEnable(GL_DEPTH_TEST)); -// glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glDepthMask(GL_FALSE)); shader->start_using(); - //BBS: reopen cul faces - m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); + shader->set_uniform("emission_factor", 0.1f); + const Camera& camera = wxGetApp().plater()->get_camera(); + m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, camera.get_view_matrix(), camera.get_projection_matrix()); + shader->set_uniform("emission_factor", 0.0f); shader->stop_using(); - // glsafe(::glDepthMask(GL_TRUE)); + glsafe(::glDepthMask(GL_TRUE)); } //BBS @@ -4168,7 +4071,8 @@ void GCodeViewer::render_all_plates_stats(const std::vector filament_diameters = gcode_result_list.front()->filament_diameters; std::vector filament_densities = gcode_result_list.front()->filament_densities; - std::vector filament_colors = decode_colors(wxGetApp().plater()->get_extruder_colors_from_plater_config(gcode_result_list.back())); + std::vector filament_colors; + decode_colors(wxGetApp().plater()->get_extruder_colors_from_plater_config(gcode_result_list.back()), filament_colors); for (int i = 0; i < filament_colors.size(); i++) { filament_colors[i] = adjust_color_for_rendering(filament_colors[i]); @@ -4213,13 +4117,13 @@ void GCodeViewer::render_all_plates_stats(const std::vector>& columns_offsets) + auto append_item = [icon_size, &imgui, imperial_units, &window_padding, &draw_list, this](const ColorRGBA& color, const std::vector>& columns_offsets) { // render icon ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y); draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + ImGuiWrapper::to_ImU32(color)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale)); @@ -4425,7 +4329,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv auto append_item = [icon_size, &imgui, imperial_units, &window_padding, &draw_list, this]( EItemType type, - const Color& color, + const ColorRGBA& color, const std::vector>& columns_offsets, bool checkbox = true, bool visible = true, @@ -4437,21 +4341,21 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv default: case EItemType::Rect: { draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + ImGuiWrapper::to_ImU32(color)); break; } case EItemType::Circle: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f)); - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 16); break; } case EItemType::Hexagon: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size + 5.0f)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 6); break; } case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size + 2 }, { pos.x + icon_size - 1, pos.y + 4 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + draw_list->AddLine({ pos.x + 1, pos.y + icon_size + 2 }, { pos.x + icon_size - 1, pos.y + 4 }, ImGuiWrapper::to_ImU32(color), 3.0f); break; case EItemType::None: break; @@ -4567,7 +4471,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv }; auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { - std::vector>> ret; + std::vector>> ret; ret.reserve(custom_gcode_per_print_z.size()); for (const auto& item : custom_gcode_per_print_z) { @@ -4587,7 +4491,11 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv // to avoid duplicate values, check adding values if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) - ret.push_back({ decode_color(item.color), { previous_z, current_z } }); + { + ColorRGBA color; + decode_color(item.color, color); + ret.push_back({ color, { previous_z, current_z } }); + } } return ret; @@ -4834,7 +4742,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } auto append_option_item = [this, append_item](EMoveType type, std::vector offsets) { - auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const Color& color, const std::string& label, bool visible) { + auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const ColorRGBA& color, const std::string& label, bool visible) { append_item(EItemType::Rect, color, {{ label , offsets[0] }}, true, visible, [this, type, visible]() { m_buffers[buffer_id(type)].visible = !m_buffers[buffer_id(type)].visible; // update buffers' render paths @@ -4958,7 +4866,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv if (need_scrollable) ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); if (m_extruder_ids.size() == 1) { // single extruder use case - const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); auto extruder_idx = m_extruder_ids[0]; if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode @@ -4990,7 +4898,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv // shows only extruders actually used size_t i = 0; for (auto extruder_idx : m_extruder_ids) { - const std::vector>> cp_values = color_print_ranges(extruder_idx, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(extruder_idx, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode const bool filament_visible = m_tools.m_tool_visibles[extruder_idx]; @@ -5119,8 +5027,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv }; EType type; int extruder_id; - Color color1; - Color color2; + ColorRGBA color1; + ColorRGBA color2; Times times; std::pair used_filament {0.0f, 0.0f}; }; @@ -5131,7 +5039,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv //BBS: replace model custom gcode with current plate custom gcode std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z; - std::vector last_color(m_extruders_count); + std::vector last_color(m_extruders_count); for (size_t i = 0; i < m_extruders_count; ++i) { last_color[i] = m_tools.m_tool_colors[i]; } @@ -5143,8 +5051,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv case CustomGCode::PausePrint: { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); - items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second }); + items.push_back({ PartialTime::EType::Pause, it->extruder, ColorRGBA::BLACK(), ColorRGBA::BLACK(), time_rec.second }); custom_gcode_per_print_z.erase(it); } break; @@ -5152,14 +5060,16 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv case CustomGCode::ColorChange: { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) }); - items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); - last_color[it->extruder - 1] = decode_color(it->color); + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) }); + ColorRGBA color; + decode_color(it->color, color); + items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], color, time_rec.second }); + last_color[it->extruder - 1] = color; last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) }); + items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id - 1) }); break; } @@ -5170,7 +5080,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv return items; }; - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { + auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); @@ -5180,16 +5090,16 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); + ImGuiWrapper::to_ImU32(color1)); pos.x += icon_size; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); + ImGuiWrapper::to_ImU32(color2)); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second - times.first))); }; - auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { + auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array& offsets, const Times& times, std::pair used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); @@ -5199,7 +5109,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + ImGuiWrapper::to_ImU32(color)); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second))); @@ -5663,13 +5573,13 @@ void GCodeViewer::render_statistics() ImGuiWrapper& imgui = *wxGetApp().imgui(); - auto add_time = [this, &imgui](const std::string& label, int64_t time) { + auto add_time = [&imgui](const std::string& label, int64_t time) { imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offset); imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast(time) * 0.001f) + ")"); }; - auto add_memory = [this, &imgui](const std::string& label, int64_t memory) { + auto add_memory = [&imgui](const std::string& label, int64_t memory) { auto format_string = [memory](const std::string& units, float value) { return std::to_string(memory) + " bytes (" + Slic3r::float_to_string_decimal_point(float(memory) * value, 3) @@ -5692,7 +5602,7 @@ void GCodeViewer::render_statistics() imgui.text(format_string("GB", inv_gb)); }; - auto add_counter = [this, &imgui](const std::string& label, int64_t counter) { + auto add_counter = [&imgui](const std::string& label, int64_t counter) { imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offset); imgui.text(std::to_string(counter)); @@ -5716,7 +5626,6 @@ void GCodeViewer::render_statistics() } if (ImGui::CollapsingHeader("OpenGL calls")) { - add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); @@ -5781,7 +5690,7 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } } -GCodeViewer::Color GCodeViewer::option_color(EMoveType move_type) const +ColorRGBA GCodeViewer::option_color(EMoveType move_type) const { switch (move_type) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 818f3a3e5e..a91e7b938e 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -34,7 +34,6 @@ static const float SLIDER_BOTTOM_MARGIN = 64.0f; class GCodeViewer { using IBufferType = unsigned short; - using Color = std::array; using VertexBuffer = std::vector; using MultiVertexBuffer = std::vector; using IndexBuffer = std::vector; @@ -43,12 +42,12 @@ class GCodeViewer using InstanceIdBuffer = std::vector; using InstancesOffsets = std::vector; - static const std::vector Extrusion_Role_Colors; - static const std::vector Options_Colors; - static const std::vector Travel_Colors; - static const std::vector Range_Colors; - static const Color Wipe_Color; - static const Color Neutral_Color; + static const std::vector Extrusion_Role_Colors; + static const std::vector Options_Colors; + static const std::vector Travel_Colors; + static const std::vector Range_Colors; + static const ColorRGBA Wipe_Color; + static const ColorRGBA Neutral_Color; enum class EOptionsColors : unsigned char { @@ -133,7 +132,7 @@ class GCodeViewer // vbo id unsigned int vbo{ 0 }; // Color to apply to the instances - Color color; + ColorRGBA color; }; std::vector ranges; @@ -256,7 +255,7 @@ class GCodeViewer // Index of the parent tbuffer unsigned char tbuffer_id; // Render path property - Color color; + ColorRGBA color; // Index of the buffer in TBuffer::indices unsigned int ibuffer_id; // Render path content @@ -276,12 +275,10 @@ class GCodeViewer bool operator() (const RenderPath &l, const RenderPath &r) const { if (l.tbuffer_id < r.tbuffer_id) return true; - for (int i = 0; i < 3; ++i) { - if (l.color[i] < r.color[i]) - return true; - else if (l.color[i] > r.color[i]) - return false; - } + if (l.color < r.color) + return true; + else if (l.color > r.color) + return false; return l.ibuffer_id < r.ibuffer_id; } }; @@ -296,7 +293,6 @@ class GCodeViewer { enum class ERenderPrimitiveType : unsigned char { - Point, Line, Triangle, InstancedModel, @@ -312,9 +308,9 @@ class GCodeViewer struct Model { GLModel model; - Color color; + ColorRGBA color; InstanceVBuffer instances; - GLModel::InitializationData data; + GLModel::Geometry data; void reset(); }; @@ -337,7 +333,6 @@ class GCodeViewer unsigned int max_vertices_per_segment() const { switch (render_primitive_type) { - case ERenderPrimitiveType::Point: { return 1; } case ERenderPrimitiveType::Line: { return 2; } case ERenderPrimitiveType::Triangle: { return 8; } default: { return 0; } @@ -349,7 +344,6 @@ class GCodeViewer unsigned int indices_per_segment() const { switch (render_primitive_type) { - case ERenderPrimitiveType::Point: { return 1; } case ERenderPrimitiveType::Line: { return 2; } case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles default: { return 0; } @@ -359,7 +353,6 @@ class GCodeViewer unsigned int max_indices_per_segment() const { switch (render_primitive_type) { - case ERenderPrimitiveType::Point: { return 1; } case ERenderPrimitiveType::Line: { return 2; } case ERenderPrimitiveType::Triangle: { return 36; } // 3 indices x 12 triangles default: { return 0; } @@ -370,14 +363,13 @@ class GCodeViewer bool has_data() const { switch (render_primitive_type) { - case ERenderPrimitiveType::Point: case ERenderPrimitiveType::Line: case ERenderPrimitiveType::Triangle: { return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; } case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); } case ERenderPrimitiveType::BatchedModel: { - return model.data.vertices_count() > 0 && model.data.indices_count() && + return !model.data.vertices.empty() && !model.data.indices.empty() && !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; } default: { return false; } @@ -416,7 +408,7 @@ class GCodeViewer void reset(bool log = false) { min = FLT_MAX; max = -FLT_MAX; count = 0; log_scale = log; } float step_size() const; - Color get_color_at(float value) const; + ColorRGBA get_color_at(float value) const; float get_value_at_step(int step) const; }; @@ -519,7 +511,7 @@ Range layer_duration_log; TBuffer* buffer{ nullptr }; unsigned int ibo{ 0 }; unsigned int vbo{ 0 }; - Color color; + ColorRGBA color; ~SequentialRangeCap(); bool is_renderable() const { return buffer != nullptr; } @@ -539,7 +531,6 @@ Range layer_duration_log; int64_t refresh_time{ 0 }; int64_t refresh_paths_time{ 0 }; // opengl calls - int64_t gl_multi_points_calls_count{ 0 }; int64_t gl_multi_lines_calls_count{ 0 }; int64_t gl_multi_triangles_calls_count{ 0 }; int64_t gl_triangles_calls_count{ 0 }; @@ -582,7 +573,6 @@ Range layer_duration_log; } void reset_opengl() { - gl_multi_points_calls_count = 0; gl_multi_lines_calls_count = 0; gl_multi_triangles_calls_count = 0; gl_triangles_calls_count = 0; @@ -645,7 +635,7 @@ public: bool is_visible() const { return m_visible; } void set_visible(bool visible) { m_visible = visible; } - void render(int canvas_width, int canvas_height, const EViewType& view_type) const; + void render(int canvas_width, int canvas_height, const EViewType& view_type); void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; } void update_curr_move(const GCodeProcessorResult::MoveVertex move); @@ -712,12 +702,12 @@ public: std::vector gcode_ids; float m_scale = 1.0; bool m_show_gcode_window = false; - void render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) const; + void render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type); }; struct ETools { - std::vector m_tool_colors; + std::vector m_tool_colors; std::vector m_tool_visibles; }; @@ -813,7 +803,7 @@ public: // extract rendering data from the given parameters //BBS: add only gcode mode void load(const GCodeProcessorResult& gcode_result, const Print& print, const BuildVolume& build_volume, - const std::vector& exclude_bounding_box, bool initialized, ConfigOptionMode mode, bool only_gcode = false); + const std::vector& exclude_bounding_box, ConfigOptionMode mode, bool only_gcode = false); // recalculate ranges in dependence of what is visible and sets tool/print colors void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); void refresh_render_paths(); @@ -823,7 +813,7 @@ public: void reset(); //BBS: always load shell at preview void reset_shell(); - void load_shells(const Print& print, bool initialized, bool force_previewing = false); + void load_shells(const Print& print, bool force_previewing = false); void set_shells_on_preview(bool is_previewing) { m_shells.previewing = is_previewing; } //BBS: add all plates filament statistics void render_all_plates_stats(const std::vector& gcode_result_list, bool show = true) const; @@ -903,7 +893,7 @@ public: private: void load_toolpaths(const GCodeProcessorResult& gcode_result, const BuildVolume& build_volume, const std::vector& exclude_bounding_box); //BBS: always load shell at preview - //void load_shells(const Print& print, bool initialized); + //void load_shells(const Print& print); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; void render_toolpaths(); void render_shells(); @@ -920,7 +910,7 @@ private: } bool is_visible(const Path& path) const { return is_visible(path.role); } void log_memory_used(const std::string& label, int64_t additional = 0) const; - Color option_color(EMoveType move_type) const; + ColorRGBA option_color(EMoveType move_type) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 036e7cbb34..1593b183f7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,3 +1,13 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, David Kocík @kocikdav, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, Vojtěch Král @vojtechkral +///|/ Copyright (c) BambuStudio 2023 manch1n @manch1n +///|/ Copyright (c) SuperSlicer 2023 Remi Durand @supermerill +///|/ Copyright (c) 2020 Benjamin Greiner +///|/ Copyright (c) 2019 John Drake @foxox +///|/ Copyright (c) 2019 BeldrothTheGold @BeldrothTheGold +///|/ Copyright (c) 2019 Thomas Moore +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r/libslic3r.h" #include "GLCanvas3D.hpp" @@ -14,22 +24,16 @@ #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PresetBundle.hpp" -#include "slic3r/GUI/3DBed.hpp" -#include "slic3r/GUI/3DScene.hpp" -#include "slic3r/GUI/BackgroundSlicingProcess.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/GLShader.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/Tab.hpp" -#include "slic3r/GUI/GUI_Preview.hpp" -#include "slic3r/GUI/OpenGLManager.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/MainFrame.hpp" -#include "slic3r/Utils/UndoRedo.hpp" -#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" -#include "slic3r/GUI/BitmapCache.hpp" -#include "slic3r/Utils/MacDarkMode.hpp" - +#include "3DBed.hpp" +#include "3DScene.hpp" +#include "BackgroundSlicingProcess.hpp" +#include "GLShader.hpp" +#include "GUI.hpp" +#include "Tab.hpp" +#include "GUI_Preview.hpp" +#include "OpenGLManager.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_Colors.hpp" @@ -38,6 +42,10 @@ #include "NotificationManager.hpp" #include "format.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" +#include "slic3r/Utils/UndoRedo.hpp" +#include "slic3r/Utils/MacDarkMode.hpp" + #include #if ENABLE_RETINA_GL @@ -78,34 +86,25 @@ static constexpr const float TRACKBALLSIZE = 0.8f; -float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[3] = { 0.906f, 0.906f, 0.906f }; -float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR_DARK[3] = { 0.329f, 0.329f, 0.353f }; -float GLCanvas3D::ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; -float GLCanvas3D::ERROR_BG_LIGHT_COLOR_DARK[3] = { 0.753f, 0.192f, 0.039f }; +static Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.906f, 0.906f, 0.906f, 1.0f }; +static Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR_DARK = { 0.329f, 0.329f, 0.353f, 1.0f }; +static Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f }; +static Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR_DARK = { 0.753f, 0.192f, 0.039f, 1.0f }; void GLCanvas3D::update_render_colors() { - GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[0] = RenderColor::colors[RenderCol_3D_Background].x; - GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[1] = RenderColor::colors[RenderCol_3D_Background].y; - GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[2] = RenderColor::colors[RenderCol_3D_Background].z; + DEFAULT_BG_LIGHT_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_3D_Background]); } void GLCanvas3D::load_render_colors() { - RenderColor::colors[RenderCol_3D_Background] = ImVec4(GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[0], - GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[1], - GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[2], - 1.0f); + RenderColor::colors[RenderCol_3D_Background] = ImGuiWrapper::to_ImVec4(DEFAULT_BG_LIGHT_COLOR); } //static constexpr const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; // Number of floats static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB -// Reserve size in number of floats. -static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB -// Reserve size in number of floats, maximum sum of all preallocated buffers. -//static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB namespace Slic3r { namespace GUI { @@ -229,9 +228,8 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv ImGuiWrapper& imgui = *wxGetApp().imgui(); const Size& cnv_size = canvas.get_canvas_size(); - float zoom = (float)wxGetApp().plater()->get_camera().get_zoom(); float left_pos = canvas.m_main_toolbar.get_item("layersediting")->render_left_pos; - const float x = 0.5 * cnv_size.get_width() + left_pos * zoom; + const float x = (1 + left_pos) * cnv_size.get_width() / 2; imgui.set_next_window_pos(x, canvas.m_main_toolbar.get_height(), ImGuiCond_Always, 0.0f, 0.0f); imgui.push_toolbar_style(canvas.get_scale()); @@ -331,9 +329,8 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) { render_variable_layer_height_dialog(canvas); - const Rect& bar_rect = get_bar_rect_viewport(canvas); - render_background_texture(canvas, bar_rect); - render_curve(bar_rect); + render_active_object_annotations(canvas); + render_profile(canvas); } float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) @@ -367,15 +364,6 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) return { w - thickness_bar_width(canvas), 0.0f, w, h }; } -Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) -{ - const Size& cnv_size = canvas.get_canvas_size(); - float half_w = 0.5f * (float)cnv_size.get_width(); - float half_h = 0.5f * (float)cnv_size.get_height(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom }; -} - bool GLCanvas3D::LayersEditing::is_initialized() const { return wxGetApp().get_shader("variable_layer_height") != nullptr; @@ -407,11 +395,19 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con return ret; } -void GLCanvas3D::LayersEditing::render_background_texture(const GLCanvas3D& canvas, const Rect& bar_rect) +void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas) { if (!m_enabled) return; + const Size cnv_size = canvas.get_canvas_size(); + const float cnv_width = (float)cnv_size.get_width(); + const float cnv_height = (float)cnv_size.get_height(); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + + const float cnv_inv_width = 1.0f / cnv_width; + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); if (shader == nullptr) return; @@ -423,59 +419,123 @@ void GLCanvas3D::LayersEditing::render_background_texture(const GLCanvas3D& canv shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); shader->set_uniform("z_cursor_band_width", band_width); shader->set_uniform("object_max_z", m_object_max_z); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); // Render the color bar - const float l = bar_rect.get_left(); - const float r = bar_rect.get_right(); - const float t = bar_rect.get_top(); - const float b = bar_rect.get_bottom(); + if (!m_profile.background.is_initialized() || m_profile.old_canvas_width != cnv_width) { + m_profile.old_canvas_width = cnv_width; + m_profile.background.reset(); - ::glBegin(GL_QUADS); - ::glNormal3f(0.0f, 0.0f, 1.0f); - ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); - ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); - ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); - ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); - glsafe(::glEnd()); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3T2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + const float l = 1.0f - 2.0f * THICKNESS_BAR_WIDTH * cnv_inv_width; + const float r = 1.0f; + const float t = 1.0f; + const float b = -1.0f; + init_data.add_vertex(Vec3f(l, b, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec3f(r, b, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec3f(r, t, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec3f(l, t, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 1.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_profile.background.init_from(std::move(init_data)); + } + + m_profile.background.render(); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); shader->stop_using(); } -void GLCanvas3D::LayersEditing::render_curve(const Rect & bar_rect) +void GLCanvas3D::LayersEditing::render_profile(const GLCanvas3D& canvas) { if (!m_enabled) return; //FIXME show some kind of legend. + if (!m_slicing_parameters) return; + const Size cnv_size = canvas.get_canvas_size(); + const float cnv_width = (float)cnv_size.get_width(); + const float cnv_height = (float)cnv_size.get_height(); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height); - const float scale_y = bar_rect.get_height() / m_object_max_z; - const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; + const float scale_x = THICKNESS_BAR_WIDTH / float(1.12 * m_slicing_parameters->max_layer_height); + const float scale_y = cnv_height / m_object_max_z; + + const float cnv_inv_width = 1.0f / cnv_width; + const float cnv_inv_height = 1.0f / cnv_height; // Baseline - glsafe(::glColor3f(0.0f, 0.0f, 0.0f)); - ::glBegin(GL_LINE_STRIP); - ::glVertex2f(x, bar_rect.get_bottom()); - ::glVertex2f(x, bar_rect.get_top()); - glsafe(::glEnd()); + if (!m_profile.baseline.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { + m_profile.baseline.reset(); - // Curve - glsafe(::glColor3f(0.0f, 0.0f, 1.0f)); - ::glBegin(GL_LINE_STRIP); - for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2) - ::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y); - glsafe(::glEnd()); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2}; + init_data.color = ColorRGBA::BLACK(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + const float axis_x = 2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_slicing_parameters->layer_height) * scale_x) * cnv_inv_width - 0.5f); + init_data.add_vertex(Vec2f(axis_x, -1.0f)); + init_data.add_vertex(Vec2f(axis_x, 1.0f)); + + // indices + init_data.add_line(0, 1); + + m_profile.baseline.init_from(std::move(init_data)); + } + + if (!m_profile.profile.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) { + m_profile.old_layer_height_profile = m_layer_height_profile; + m_profile.profile.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = ColorRGBA::BLUE(); + init_data.reserve_vertices(m_layer_height_profile.size() / 2); + init_data.reserve_indices(m_layer_height_profile.size() / 2); + + // vertices + indices + for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) { + init_data.add_vertex(Vec2f(2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_layer_height_profile[i + 1]) * scale_x) * cnv_inv_width - 0.5f), + 2.0f * (float(m_layer_height_profile[i]) * scale_y * cnv_inv_height - 0.5))); + init_data.add_index(i / 2); + } + + m_profile.profile.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + m_profile.baseline.render(); + m_profile.profile.render(); + shader->stop_using(); + } } -void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D & canvas, const GLVolumeCollection & volumes)//render volume and layer height texture (has mapping relation with each other) +void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes) { assert(this->is_allowed()); assert(this->last_object_id != -1); @@ -499,6 +559,9 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D & canvas, const shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); shader->set_uniform("z_cursor_band_width", float(this->band_width)); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + // Initialize the layer height texture mapping. const GLsizei w = (GLsizei)m_layers_texture.width; const GLsizei h = (GLsizei)m_layers_texture.height; @@ -517,6 +580,11 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D & canvas, const shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); shader->set_uniform("object_max_z", 0.0f); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d model_matrix = glvolume->world_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); glvolume->render(); } @@ -619,49 +687,6 @@ float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D & canvas) * THICKNESS_BAR_WIDTH; } -Size::Size() - : m_width(0) - , m_height(0) -{ -} - -Size::Size(int width, int height, float scale_factor) - : m_width(width) - , m_height(height) - , m_scale_factor(scale_factor) -{ -} - -int Size::get_width() const -{ - return m_width; -} - -void Size::set_width(int width) -{ - m_width = width; -} - -int Size::get_height() const -{ - return m_height; -} - -void Size::set_height(int height) -{ - m_height = height; -} - -int Size::get_scale_factor() const -{ - return m_scale_factor; -} - -void Size::set_scale_factor(int scale_factor) -{ - m_scale_factor = scale_factor; -} - const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5; @@ -879,113 +904,81 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons m_perimeter.reset(); m_fill.reset(); if (!polygons.empty()) { - size_t triangles_count = 0; - for (const Polygon &poly : polygons) { triangles_count += poly.points.size() - 2; } - const size_t vertices_count = 3 * triangles_count; - if (m_render_fill) { - GLModel::InitializationData fill_data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; - entity.color = {0.8f, 0.8f, 1.0f, 0.5f}; - entity.positions.reserve(vertices_count); - entity.normals.reserve(vertices_count); - entity.indices.reserve(vertices_count); + GLModel::Geometry fill_data; + fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + fill_data.color = { 0.8f, 0.8f, 1.0f, 0.5f }; + // vertices + indices const ExPolygons polygons_union = union_ex(polygons); - for (const ExPolygon &poly : polygons_union) { + unsigned int vertices_counter = 0; + for (const ExPolygon& poly : polygons_union) { const std::vector triangulation = triangulate_expolygon_3d(poly); - for (const Vec3d &v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting - entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t positions_count = entity.positions.size(); - if (positions_count % 3 == 0) { - entity.indices.emplace_back(positions_count - 3); - entity.indices.emplace_back(positions_count - 2); - entity.indices.emplace_back(positions_count - 1); - } + fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size()); + fill_data.reserve_indices(fill_data.indices_count() + triangulation.size()); + for (const Vec3d& v : triangulation) { + fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } } - fill_data.entities.emplace_back(entity); - m_fill.init_from(fill_data); + m_fill.init_from(std::move(fill_data)); } - GLModel::InitializationData perimeter_data; - for (const Polygon &poly : polygons) { - GLModel::InitializationData::Entity ent; - ent.type = GLModel::PrimitiveType::LineLoop; - ent.positions.reserve(poly.points.size()); - ent.indices.reserve(poly.points.size()); - unsigned int id_count = 0; - for (const Point &p : poly.points) { - ent.positions.emplace_back(unscale(p.x()), unscale(p.y()), 0.025f); // add a small positive z to avoid z-fighting - ent.normals.emplace_back(Vec3f::UnitZ()); - ent.indices.emplace_back(id_count++); - } - - perimeter_data.entities.emplace_back(ent); - } - - m_perimeter.init_from(perimeter_data); + m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting } //BBS: add the height limit compute logic if (!height_polygons.empty()) { - size_t height_triangles_count = 0; - for (const auto &poly : height_polygons) { height_triangles_count += poly.first.points.size() - 2; } - const size_t height_vertices_count = 3 * height_triangles_count; - - GLModel::InitializationData height_fill_data; - GLModel::InitializationData::Entity height_entity; - height_entity.type = GLModel::PrimitiveType::Triangles; - height_entity.color = {0.8f, 0.8f, 1.0f, 0.5f}; - height_entity.positions.reserve(height_vertices_count); - height_entity.normals.reserve(height_vertices_count); - height_entity.indices.reserve(height_vertices_count); + GLModel::Geometry height_fill_data; + height_fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + height_fill_data.color = {0.8f, 0.8f, 1.0f, 0.5f}; + // vertices + indices + unsigned int vertices_counter = 0; for (const auto &poly : height_polygons) { ExPolygon ex_poly(poly.first); const std::vector height_triangulation = triangulate_expolygon_3d(ex_poly); for (const Vec3d &v : height_triangulation) { - Vec3d point{v.x(), v.y(), poly.second}; - height_entity.positions.emplace_back(point.cast()); - height_entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t height_positions_count = height_entity.positions.size(); - if (height_positions_count % 3 == 0) { - height_entity.indices.emplace_back(height_positions_count - 3); - height_entity.indices.emplace_back(height_positions_count - 2); - height_entity.indices.emplace_back(height_positions_count - 1); - } + Vec3f point{(float) v.x(), (float) v.y(), poly.second}; + height_fill_data.add_vertex(point); + ++vertices_counter; + if (vertices_counter % 3 == 0) + height_fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } } - height_fill_data.entities.emplace_back(height_entity); - m_height_limit.init_from(height_fill_data); + m_height_limit.init_from(std::move(height_fill_data)); } } void GLCanvas3D::SequentialPrintClearance::render() { - std::array FILL_COLOR = { 0.7f, 0.7f, 1.0f, 0.5f }; - std::array NO_FILL_COLOR = { 0.75f, 0.75f, 0.75f, 0.75f }; + const ColorRGBA FILL_COLOR = { 0.7f, 0.7f, 1.0f, 0.5f }; + const ColorRGBA NO_FILL_COLOR = { 0.75f, 0.75f, 0.75f, 0.75f }; - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader == nullptr) return; shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); + m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); m_perimeter.render(); m_fill.render(); //BBS: add height limit - m_height_limit.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); + m_height_limit.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); m_height_limit.render(); glsafe(::glDisable(GL_BLEND)); @@ -1141,11 +1134,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) , m_moving(false) , m_tab_down(false) , m_cursor_type(Standard) - , m_color_by("volume") , m_reload_delayed(false) -#if ENABLE_RENDER_PICKING_PASS - , m_show_picking_texture(false) -#endif // ENABLE_RENDER_PICKING_PASS , m_render_sla_auxiliaries(true) , m_labels(*this) , m_slope(m_volumes) @@ -1199,36 +1188,6 @@ bool GLCanvas3D::init() glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - // Set antialiasing / multisampling - glsafe(::glDisable(GL_LINE_SMOOTH)); - glsafe(::glDisable(GL_POLYGON_SMOOTH)); - - // ambient lighting - GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; - glsafe(::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient)); - - glsafe(::glEnable(GL_LIGHT0)); - glsafe(::glEnable(GL_LIGHT1)); - - // light from camera - GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam)); - GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam)); - - // light from above - GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top)); - GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top)); - - // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. - glsafe(::glShadeModel(GL_SMOOTH)); - - // A handy trick -- have surface material mirror the color. - glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)); - glsafe(::glEnable(GL_COLOR_MATERIAL)); - if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); @@ -1236,10 +1195,6 @@ bool GLCanvas3D::init() if (m_main_toolbar.is_enabled()) m_layers_editing.init(); - // on linux the gl context is not valid until the canvas is not shown on screen - // we defer the geometry finalization of volumes until the first call to render() - m_volumes.finalize_geometry(true); - BOOST_LOG_TRIVIAL(info) <<__FUNCTION__<< ": before gizmo init"; if (m_gizmos.is_enabled() && !m_gizmos.init()) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; @@ -1386,21 +1341,31 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) { + if (current_printer_technology() != ptSLA) + return; + m_render_sla_auxiliaries = visible; + std::vector>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume); + for (GLVolume* vol : m_volumes.volumes) { if (vol->composite_id.object_id >= 1000 && vol->composite_id.object_id < 1000 + wxGetApp().plater()->get_partplate_list().get_plate_count()) continue; // the wipe tower - if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) - && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) - && vol->composite_id.volume_id < 0) + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) + && vol->composite_id.volume_id < 0) { vol->is_active = visible; + auto it = std::find_if(raycasters->begin(), raycasters->end(), [vol](std::shared_ptr item) { return item->get_raycaster() == vol->mesh_raycaster.get(); }); + if (it != raycasters->end()) + (*it)->set_active(vol->is_active); + } } } void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv) { + std::vector>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume); for (GLVolume* vol : m_volumes.volumes) { // BBS: add partplate logic if (vol->composite_id.object_id >= 1000 && @@ -1412,6 +1377,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) { vol->is_active = visible; + if (!vol->is_modifier) + vol->color.a(1.f); if (instance_idx == -1) { vol->force_native_color = false; @@ -1419,10 +1386,12 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject } else { const GLGizmosManager& gm = get_gizmos_manager(); auto gizmo_type = gm.get_current_type(); - if ( (gizmo_type == GLGizmosManager::FdmSupports - || gizmo_type == GLGizmosManager::Seam) - && ! vol->is_modifier) + if ( (gizmo_type == GLGizmosManager::FdmSupports + || gizmo_type == GLGizmosManager::Seam + || gizmo_type == GLGizmosManager::Cut) + && !vol->is_modifier) { vol->force_neutral_color = true; + } else if (gizmo_type == GLGizmosManager::MmuSegmentation) vol->is_active = false; else @@ -1430,7 +1399,12 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject } } } + + auto it = std::find_if(raycasters->begin(), raycasters->end(), [vol](std::shared_ptr item) { return item->get_raycaster() == vol->mesh_raycaster.get(); }); + if (it != raycasters->end()) + (*it)->set_active(vol->is_active); } + if (visible && !mo) toggle_sla_auxiliaries_visibility(true, mo, instance_idx); @@ -1498,11 +1472,6 @@ Camera& GLCanvas3D::get_camera() return camera; } -void GLCanvas3D::set_color_by(const std::string& value) -{ - m_color_by = value; -} - void GLCanvas3D::refresh_camera_scene_box() { wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box()); @@ -1847,7 +1816,8 @@ void GLCanvas3D::render(bool only_init) // and the viewport was set incorrectly, leading to tripping glAsserts further down // the road (in apply_projection). That's why the minimum size is forced to 10. Camera& camera = wxGetApp().plater()->get_camera(); - camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); + camera.set_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); + camera.apply_viewport(); if (camera.requires_zoom_to_bed) { zoom_to_bed(); @@ -1867,14 +1837,8 @@ void GLCanvas3D::render(bool only_init) camera.requires_zoom_to_volumes = false; } - camera.apply_view_matrix(); camera.apply_projection(_max_bounding_box(true, true, true)); - GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; - glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); - GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; - glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top)); - wxGetApp().imgui()->new_frame(); if (m_picking_enabled) { @@ -1883,14 +1847,19 @@ void GLCanvas3D::render(bool only_init) _rectangular_selection_picking_pass(); //BBS: enable picking when no volumes for partplate logic //else if (!m_volumes.empty()) - else + else { // regular picking pass _picking_pass(); + +#if ENABLE_RAYCAST_PICKING_DEBUG + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + imgui.text("Picking disabled"); + imgui.end(); +#endif // ENABLE_RAYCAST_PICKING_DEBUG + } } -#if ENABLE_RENDER_PICKING_PASS - if (!m_picking_enabled || !m_show_picking_texture) { -#endif // ENABLE_RENDER_PICKING_PASS // draw scene glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); _render_background(); @@ -1913,9 +1882,9 @@ void GLCanvas3D::render(bool only_init) _render_sla_slices(); _render_selection(); if (!no_partplate) - _render_bed(!camera.is_looking_downward(), show_axes); + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes); if (!no_partplate) //BBS: add outline logic - _render_platelist(!camera.is_looking_downward(), only_current, only_body, hover_id, true); + _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true); _render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running()); } /* preview render */ @@ -1923,8 +1892,8 @@ void GLCanvas3D::render(bool only_init) _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); _render_sla_slices(); _render_selection(); - _render_bed(!camera.is_looking_downward(), show_axes); - _render_platelist(!camera.is_looking_downward(), only_current, true, hover_id); + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes); + _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id); // BBS: GUI refactor: add canvas size as parameters _render_gcode(cnv_size.get_width(), cnv_size.get_height()); } @@ -1932,7 +1901,7 @@ void GLCanvas3D::render(bool only_init) else if (m_canvas_type == ECanvasType::CanvasAssembleView) { //BBS: add outline logic _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); - //_render_bed(!camera.is_looking_downward(), show_axes); + //_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes); _render_plane(); //BBS: add outline logic insteadof selection under assemble view //_render_selection(); @@ -1955,9 +1924,11 @@ void GLCanvas3D::render(bool only_init) // could be invalidated by the following gizmo render methods _render_selection_sidebar_hints(); _render_current_gizmo(); -#if ENABLE_RENDER_PICKING_PASS - } -#endif // ENABLE_RENDER_PICKING_PASS + +#if ENABLE_RAYCAST_PICKING_DEBUG + if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging()) + m_scene_raycaster.render_hit(camera); +#endif // ENABLE_RAYCAST_PICKING_DEBUG #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); @@ -2068,7 +2039,7 @@ void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, { GLShaderProgram* shader = wxGetApp().get_shader("thumbnail"); ModelObjectPtrs& model_objects = GUI::wxGetApp().model().objects; - std::vector> colors = ::get_extruders_colors(); + std::vector colors = ::get_extruders_colors(); switch (OpenGLManager::get_framebuffers_type()) { case OpenGLManager::EFramebufferType::Arb: @@ -2130,8 +2101,8 @@ void GLCanvas3D::set_selected_visible(bool visible) for (unsigned int i : m_selection.get_volume_idxs()) { GLVolume* volume = const_cast(m_selection.get_volume(i)); volume->visible = visible; - volume->color[3] = visible ? 1.f : GLVolume::MODEL_HIDDEN_COL[3]; - volume->render_color[3] = volume->color[3]; + volume->color.a(visible ? 1.f : GLVolume::MODEL_HIDDEN_COL.a()); + volume->render_color.a(volume->color.a()); volume->force_native_color = !visible; } } @@ -2202,7 +2173,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.emplace_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs); } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -2237,7 +2208,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (!m_initialized) return; - + _set_current(); m_hover_volume_idxs.clear(); @@ -2513,7 +2484,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh // later in this function. it->volume_idx = m_volumes.volumes.size(); - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized, m_canvas_type == ECanvasType::CanvasAssembleView); + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_canvas_type == ECanvasType::CanvasAssembleView); m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; } else { @@ -2570,31 +2541,35 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re GLVolume &volume = *m_volumes.volumes[it->volume_idx]; if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. - volume.indexed_vertex_array.release_geometry(); - if (state.step[istep].state == PrintStateBase::DONE) { + volume.model.reset(); + if (state.step[istep].state == PrintStateBase::DONE) { TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); assert(! mesh.empty()); mesh.transform(sla_print->sla_trafo(*m_model->objects[volume.object_idx()]).inverse()); #if ENABLE_SMOOTH_NORMALS - volume.indexed_vertex_array.load_mesh(mesh, true); + volume.model.init_from(mesh, true); #else - volume.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_SMOOTH_NORMALS - } else { - // Reload the original volume. -#if ENABLE_SMOOTH_NORMALS - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); + volume.model.init_from(mesh); + volume.mesh_raycaster = std::make_unique(std::make_shared(mesh)); +#endif // ENABLE_SMOOTH_NORMALS + } + else { + // Reload the original volume. +#if ENABLE_SMOOTH_NORMALS + volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#else + const TriangleMesh& new_mesh = m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(); + volume.model.init_from(new_mesh); + volume.mesh_raycaster = std::make_unique(std::make_shared(new_mesh)); #endif // ENABLE_SMOOTH_NORMALS } - volume.finalize_geometry(true); } //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables // of various concenrs (model vs. 3D print path). volume.offsets = { state.step[istep].timestamp }; - } else if (state.step[istep].state == PrintStateBase::DONE) { + } + else if (state.step[istep].state == PrintStateBase::DONE) { // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); @@ -2606,7 +2581,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); else shift_zs[object_idx] = 0.; - } else { + } + else { // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); @@ -2616,7 +2592,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re for (size_t istep = 0; istep < sla_steps.size(); ++istep) if (!instances[istep].empty()) - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); } // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed @@ -2680,7 +2656,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( 1000 + plate_id, x + plate_origin(0), y + plate_origin(1), (float)wipe_tower_size(0), (float)wipe_tower_size(1), (float)wipe_tower_size(2), a, - /*!print->is_step_done(psWipeTower)*/ true, brim_width, m_initialized); + /*!print->is_step_done(psWipeTower)*/ true, brim_width); int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id]; if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; @@ -2695,6 +2671,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re else m_selection.volumes_changed(map_glvolume_old_to_new); + // @Enrico suggest this solution to preven accessing pointer on caster without data + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Bed); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume); m_gizmos.update_data(); m_gizmos.update_assemble_view_data(); m_gizmos.refresh_on_off_state(); @@ -2702,9 +2681,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Update the toolbar //BBS: notify the PartPlateList to reload all objects if (update_object_list) - { post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - } //BBS:exclude the assmble view if (m_canvas_type != ECanvasType::CanvasAssembleView) { @@ -2749,30 +2726,37 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re #endif } + // refresh bed raycasters for picking + if (m_canvas_type == ECanvasType::CanvasView3D) { + wxGetApp().plater()->get_partplate_list().register_raycasters_for_picking(*this); + } + + // refresh volume raycasters for picking + for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { + const GLVolume* v = m_volumes.volumes[i]; + assert(v->mesh_raycaster != nullptr); + std::shared_ptr raycaster = add_raycaster_for_picking(SceneRaycaster::EType::Volume, i, *v->mesh_raycaster, v->world_matrix()); + raycaster->set_active(v->is_active); + } + + // refresh gizmo elements raycasters for picking + GLGizmoBase* curr_gizmo = m_gizmos.get_current(); + if (curr_gizmo != nullptr) + curr_gizmo->unregister_raycasters_for_picking(); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo); + if (curr_gizmo != nullptr && !m_selection.is_empty()) + curr_gizmo->register_raycasters_for_picking(); + // and force this canvas to be redrawn. m_dirty = true; } -static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE) -{ - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol_old.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol_old.indexed_vertex_array = vol_new.indexed_vertex_array; - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - // Reserving number of vertices (3x position + 3x color) - vol_new.indexed_vertex_array.reserve(prealloc_size / 6); - // Finalize the old geometry, possibly move data to the graphics card. - vol_old.finalize_geometry(gl_initialized); -} - void GLCanvas3D::load_shells(const Print& print, bool force_previewing) { if (m_initialized) { - m_gcode_viewer.load_shells(print, m_initialized, force_previewing); + m_gcode_viewer.load_shells(print, force_previewing); m_gcode_viewer.update_shells_color_by_extruder(m_config); } } @@ -2792,7 +2776,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co //when load gcode directly, it is too late m_gcode_viewer.init(wxGetApp().get_mode(), wxGetApp().preset_bundle); m_gcode_viewer.load(gcode_result, *this->fff_print(), wxGetApp().plater()->build_volume(), exclude_bounding_box, - m_initialized, wxGetApp().get_mode(), only_gcode); + wxGetApp().get_mode(), only_gcode); if (wxGetApp().is_editor()) { //BBS: always load shell at preview, do this in load_shells @@ -3236,14 +3220,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) //} //case 'O': //case 'o': { _update_camera_zoom(-1.0); break; } -#if ENABLE_RENDER_PICKING_PASS - case 'T': - case 't': { - m_show_picking_texture = !m_show_picking_texture; - m_dirty = true; - break; - } -#endif // ENABLE_RENDER_PICKING_PASS //case 'Z': //case 'z': { // if (!m_selection.is_empty()) @@ -3369,7 +3345,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) m_dirty = true; }, [this](const Vec3d& direction, bool slow, bool camera_space) { - m_selection.start_dragging(); + m_selection.setup_cache(); double multiplier = slow ? 1.0 : 10.0; Vec3d displacement; @@ -3382,7 +3358,6 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) displacement = multiplier * direction; m_selection.translate(displacement); - m_selection.stop_dragging(); m_dirty = true; } );} @@ -3503,14 +3478,14 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) if (keyCode == WXK_SHIFT) { translationProcessor.process(evt); - if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) + if (m_picking_enabled /*&& (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)*/) { m_mouse.ignore_left_up = false; // set_cursor(Cross); } } else if (keyCode == WXK_ALT) { - if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) + if (m_picking_enabled /*&& (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)*/) { m_mouse.ignore_left_up = false; // set_cursor(Cross); @@ -3523,9 +3498,8 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); } else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) { auto _do_rotate = [this](double angle_z_rad) { - m_selection.start_dragging(); + m_selection.setup_cache(); m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint)); - m_selection.stop_dragging(); m_dirty = true; // wxGetApp().obj_manipul()->set_dirty(); }; @@ -3960,30 +3934,21 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_3D_as_invalid(); m_mouse.position = pos.cast(); - if (evt.Dragging() && current_printer_technology() == ptFFF && (fff_print()->config().print_sequence == PrintSequence::ByObject)) { - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Move: - case GLGizmosManager::EType::Scale: - case GLGizmosManager::EType::Rotate: - { - update_sequential_clearance(); - break; - } - default: { break; } - } - } - else if (evt.Dragging()) { - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Move: - case GLGizmosManager::EType::Scale: - case GLGizmosManager::EType::Rotate: - { - show_sinking_contours(); - break; - } - default: { break; } + // It should be detection of volume change + // Not only detection of some modifiers !!! + if (evt.Dragging()) { + GLGizmosManager::EType c = m_gizmos.get_current_type(); + if (current_printer_technology() == ptFFF && + (fff_print()->config().print_sequence == PrintSequence::ByObject)) { + if (c == GLGizmosManager::EType::Move || + c == GLGizmosManager::EType::Scale || + c == GLGizmosManager::EType::Rotate ) + update_sequential_clearance(); + } else { + if (c == GLGizmosManager::EType::Move || + c == GLGizmosManager::EType::Scale || + c == GLGizmosManager::EType::Rotate) + show_sinking_contours(); } } else if (evt.LeftUp() && @@ -4060,9 +4025,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // BBS: define Alt key to enable volume selection mode m_selection.set_volume_selection_mode(evt.AltDown() ? Selection::Volume : Selection::Instance); if (evt.LeftDown() && evt.ShiftDown() && m_picking_enabled && m_layers_editing.state != LayersEditing::Editing) { - if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports - && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports + if (/*m_gizmos.get_current_type() != GLGizmosManager::SlaSupports + &&*/ m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && m_gizmos.get_current_type() != GLGizmosManager::Seam + && m_gizmos.get_current_type() != GLGizmosManager::Cut && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); m_dirty = true; @@ -4112,11 +4078,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int volume_idx = get_first_hover_volume_idx(); BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); volume_bbox.offset(1.0); - if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) { + const bool is_cut_connector_selected = m_selection.is_any_connector(); + if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) { m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; // The dragging operation is initiated. m_mouse.drag.move_volume_idx = volume_idx; - m_selection.start_dragging(); + m_selection.setup_cache(); m_mouse.drag.start_position_3D = m_mouse.scene_position; m_sequential_print_clearance_first_displacement = true; m_moving = true; @@ -4281,8 +4248,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) { + m_mouse.position = pos.cast(); + if (evt.LeftUp()) { - m_selection.stop_dragging(); m_rotation_center(0) = m_rotation_center(1) = m_rotation_center(2) = 0.f; } @@ -4323,7 +4291,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) deselect_all(); } else if (evt.RightUp() && !is_layers_editing_enabled()) { - m_mouse.position = pos.cast(); // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while // the context menu is already shown render(); @@ -4331,7 +4298,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // if right clicking on volume, propagate event through callback (shows context menu) int volume_idx = get_first_hover_volume_idx(); if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open + /*&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports*/) // disable context menu when the gizmo is open { // forces the selection of the volume /* m_selection.add(volume_idx); // #et_FIXME_if_needed @@ -4576,12 +4543,13 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { const ModelObject* obj = m_model->objects[i]; for (int j = 0; j < static_cast(obj->instances.size()); ++j) { - if (snapshot_type.empty() && m_selection.get_object_idx() == i) { + if (snapshot_type == L("Gizmo-Place on Face") && m_selection.get_object_idx() == i) { // This means we are flattening this object. In that case pretend // that it is not sinking (even if it is), so it is placed on bed // later on (whatever is sinking will be left sinking). min_zs[{ i, j }] = SINKING_Z_THRESHOLD; - } else + } + else min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z(); } @@ -4732,15 +4700,6 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) m_dirty = true; } -void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type) -{ - if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(snapshot_type); - - m_selection.flattening_rotate(normal); - do_rotate(""); // avoid taking another snapshot -} - void GLCanvas3D::do_center() { if (m_model == nullptr) @@ -5267,7 +5226,7 @@ bool GLCanvas3D::_render_orient_menu(float left, float right, float bottom, floa //original use center as {0.0}, and top is (canvas_h/2), bottom is (-canvas_h/2), also plus inv_camera //now change to left_up as {0,0}, and top is 0, bottom is canvas_h #if BBS_TOOLBAR_ON_TOP - const float x = left * float(wxGetApp().plater()->get_camera().get_zoom()) + 0.5f * canvas_w; + const float x = (1 + left) * canvas_w / 2; ImGuiWrapper::push_toolbar_style(get_scale()); imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); #else @@ -5352,9 +5311,8 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo //original use center as {0.0}, and top is (canvas_h/2), bottom is (-canvas_h/2), also plus inv_camera //now change to left_up as {0,0}, and top is 0, bottom is canvas_h #if BBS_TOOLBAR_ON_TOP - float zoom = (float)wxGetApp().plater()->get_camera().get_zoom(); float left_pos = m_main_toolbar.get_item("arrange")->render_left_pos; - const float x = 0.5 * canvas_w + left_pos * zoom; + const float x = (1 + left_pos) * canvas_w / 2; imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.0f, 0.0f); #else @@ -5526,7 +5484,7 @@ static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, - PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector>& extruder_colors, + PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type, bool use_top_view, bool for_picking) { //BBS modify visible calc function @@ -5563,9 +5521,7 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const return ret; }; - static std::array curr_color; - static const std::array orange = { 0.923f, 0.504f, 0.264f, 1.0f }; - static const std::array gray = { 0.64f, 0.64f, 0.64f, 1.0f }; + static ColorRGBA curr_color; GLVolumePtrs visible_volumes; @@ -5603,7 +5559,8 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const //BBS modify scene box to plate scene bounding box //plate_build_volume.min(2) = - plate_build_volume.max(2); camera.set_scene_box(plate_build_volume); - camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.set_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.apply_viewport(); //BoundingBoxf3 plate_box = plate->get_bounding_box(false); //plate_box.min.z() = 0.0; @@ -5625,15 +5582,10 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const } else { camera.zoom_to_box(volumes_box); - const Vec3d& target = camera.get_target(); - double distance = camera.get_distance(); camera.select_view("iso"); - camera.apply_view_matrix(); - - camera.apply_projection(plate_build_volume); } - camera.apply_view_matrix(); + const Transform3d &view_matrix = camera.get_view_matrix(); camera.apply_projection(plate_build_volume); @@ -5650,6 +5602,8 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); + const Transform3d &projection_matrix = camera.get_projection_matrix(); + if (for_picking) { //if (OpenGLManager::can_multisample()) // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. @@ -5662,9 +5616,6 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); - //glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - //glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - for (GLVolume* vol : visible_volumes) { // Object picking mode. Render the object with a color encoding the object index. // we reserve color = (0,0,0) for occluders (as the printbed) @@ -5676,16 +5627,21 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const unsigned int g = (id & (0x000000FF << 8)) >> 8; unsigned int b = (id & (0x000000FF << 16)) >> 16; unsigned int a = 0xFF; - glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); + vol->model.set_color({(GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255}); /*curr_color[0] = (GLfloat)r * INV_255; curr_color[1] = (GLfloat)g * INV_255; curr_color[2] = (GLfloat)b * INV_255; curr_color[3] = (GLfloat)a * INV_255; shader->set_uniform("uniform_color", curr_color);*/ - bool is_active = vol->is_active; + const bool is_active = vol->is_active; vol->is_active = true; - vol->simple_render(nullptr, model_objects, extruder_colors); + const Transform3d model_matrix = vol->world_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + vol->simple_render(shader, model_objects, extruder_colors); vol->is_active = is_active; } @@ -5702,13 +5658,10 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const shader->set_uniform("emission_factor", 0.1f); for (GLVolume* vol : visible_volumes) { //BBS set render color for thumbnails - curr_color[0] = vol->color[0]; - curr_color[1] = vol->color[1]; - curr_color[2] = vol->color[2]; - curr_color[3] = vol->color[3]; + curr_color = vol->color; - std::array new_color = adjust_color_for_rendering(curr_color); - shader->set_uniform("uniform_color", new_color); + ColorRGBA new_color = adjust_color_for_rendering(curr_color); + vol->model.set_color(new_color); shader->set_uniform("volume_world_matrix", vol->world_matrix()); //BBS set all volume to orange //shader->set_uniform("uniform_color", orange); @@ -5719,8 +5672,13 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray); }*/ // the volume may have been deactivated by an active gizmo - bool is_active = vol->is_active; + const bool is_active = vol->is_active; vol->is_active = true; + const Transform3d model_matrix = vol->world_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); vol->simple_render(shader, model_objects, extruder_colors); vol->is_active = is_active; } @@ -5739,7 +5697,7 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const } void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, - PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector>& extruder_colors, + PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type, bool use_top_view, bool for_picking) { thumbnail_data.set(w, h); @@ -5847,7 +5805,7 @@ void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, uns } void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, - PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector>& extruder_colors, + PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type, bool use_top_view, bool for_picking) { thumbnail_data.set(w, h); @@ -5949,7 +5907,7 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, // glsafe(::glDisable(GL_MULTISAMPLE)); } -void GLCanvas3D::render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList &partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector>& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type) +void GLCanvas3D::render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList &partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type) { // check that thumbnail size does not exceed the default framebuffer size const Size& cnv_size = get_canvas_size(); @@ -5973,7 +5931,7 @@ void GLCanvas3D::render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT // restore the default framebuffer size to avoid flickering on the 3D scene - //wxGetApp().plater()->get_camera().apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height()); + //wxGetApp().plater()->get_camera().apply_viewport(); } //BBS: GUI refractor @@ -6075,21 +6033,12 @@ bool GLCanvas3D::_init_main_toolbar() return true; } // init arrow - BackgroundTexture::Metadata arrow_data; - arrow_data.filename = "toolbar_arrow.svg"; - arrow_data.left = 0; - arrow_data.top = 0; - arrow_data.right = 0; - arrow_data.bottom = 0; - if (!m_main_toolbar.init_arrow(arrow_data)) - { + if (!m_main_toolbar.init_arrow("toolbar_arrow.svg")) BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture."; - } + // m_gizmos is created at constructor, thus we can init arrow here. - if (!m_gizmos.init_arrow(arrow_data)) - { + if (!m_gizmos.init_arrow("toolbar_arrow.svg")) BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture."; - } m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); //BBS: main toolbar is at the top and left, we don't need the rounded-corner effect at the right side and the top side @@ -6474,92 +6423,176 @@ void GLCanvas3D::_refresh_if_shown_on_screen() void GLCanvas3D::_picking_pass() { - std::vector* hover_volume_idxs = const_cast*>(&m_hover_volume_idxs); - std::vector* hover_plate_idxs = const_cast*>(&m_hover_plate_idxs); + if (!m_picking_enabled || m_mouse.dragging || m_mouse.position == Vec2d(DBL_MAX, DBL_MAX) || m_gizmos.is_dragging()) { +#if ENABLE_RAYCAST_PICKING_DEBUG + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + imgui.text("Picking disabled"); + imgui.end(); +#endif // ENABLE_RAYCAST_PICKING_DEBUG + return; + } - if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) { - hover_volume_idxs->clear(); - hover_plate_idxs->clear(); + m_hover_volume_idxs.clear(); + m_hover_plate_idxs.clear(); - // Render the object for picking. - // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. - // Better to use software ray - casting on a bounding - box hierarchy. - - if (m_multisample_allowed) - // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. - glsafe(::glDisable(GL_MULTISAMPLE)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - - //BBS: only render plate in view 3D - if (m_canvas_type == ECanvasType::CanvasView3D) { - _render_plates_for_picking(); - } - - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - if (m_camera_clipping_plane.is_active()) { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); - ::glEnable(GL_CLIP_PLANE0); - } - _render_volumes_for_picking(); - if (m_camera_clipping_plane.is_active()) - ::glDisable(GL_CLIP_PLANE0); - - //BBS: remove the bed picking logic - //_render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); - - m_gizmos.render_current_gizmo_for_picking_pass(); - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - - int volume_id = -1; - int gizmo_id = -1; - - GLubyte color[4] = { 0, 0, 0, 0 }; - const Size& cnv_size = get_canvas_size(); - bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height(); - if (inside) { - glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); - if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) { - // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - // we reserve color = (0,0,0) for occluders (as the printbed) - // volumes' id are shifted by 1 - // see: _render_volumes_for_picking() - //BBS: remove the bed picking logic - //volume_id = color[0] + (color[1] << 8) + (color[2] << 16) - 1; - volume_id = color[0] + (color[1] << 8) + (color[2] << 16); - // gizmos' id are instead properly encoded by the color - gizmo_id = color[0] + (color[1] << 8) + (color[2] << 16); - } - } - else - m_gizmos.set_hover_id(inside && (unsigned int)gizmo_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - gizmo_id) : -1); - - //BBS: add plate picking logic - int plate_hover_id = PartPlate::PLATE_BASE_ID - volume_id; - if (plate_hover_id >= 0 && plate_hover_id < PartPlateList::MAX_PLATES_COUNT * PartPlate::GRABBER_COUNT) { - wxGetApp().plater()->get_partplate_list().set_hover_id(plate_hover_id); - hover_plate_idxs->emplace_back(plate_hover_id); - const_cast(&m_gizmos)->set_hover_id(-1); - } - else { - wxGetApp().plater()->get_partplate_list().reset_hover_id(); - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { - // do not add the volume id if any gizmo is active and CTRL is pressed - if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) - hover_volume_idxs->emplace_back(volume_id); - const_cast(&m_gizmos)->set_hover_id(-1); + // Orca: ignore clipping plane if not applying + GLGizmoBase *current_gizmo = m_gizmos.get_current(); + const ClippingPlane clipping_plane = ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() : + ClippingPlane::ClipsNothing()) + .inverted_normal(); + const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(m_mouse.position, wxGetApp().plater()->get_camera(), &clipping_plane); + if (hit.is_valid()) { + switch (hit.type) + { + case SceneRaycaster::EType::Volume: + { + if (0 <= hit.raycaster_id && hit.raycaster_id < (int)m_volumes.volumes.size()) { + const GLVolume* volume = m_volumes.volumes[hit.raycaster_id]; + if (volume->is_active && !volume->disabled && (volume->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { + // do not add the volume id if any gizmo is active and CTRL is pressed + if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) + m_hover_volume_idxs.emplace_back(hit.raycaster_id); + m_gizmos.set_hover_id(-1); + } } else - const_cast(&m_gizmos)->set_hover_id(inside && (unsigned int)volume_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - volume_id) : -1); - } + assert(false); - _update_volumes_hover_state(); + break; + } + case SceneRaycaster::EType::Gizmo: + case SceneRaycaster::EType::FallbackGizmo: + { + const Size& cnv_size = get_canvas_size(); + const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() && + 0 <= m_mouse.position.y() && m_mouse.position.y() < cnv_size.get_height(); + m_gizmos.set_hover_id(inside ? hit.raycaster_id : -1); + break; + } + case SceneRaycaster::EType::Bed: + { + // BBS: add plate picking logic + int plate_hover_id = PartPlate::PLATE_BASE_ID - hit.raycaster_id; + if (plate_hover_id >= 0 && plate_hover_id < PartPlateList::MAX_PLATES_COUNT * PartPlate::GRABBER_COUNT) { + wxGetApp().plater()->get_partplate_list().set_hover_id(plate_hover_id); + m_hover_plate_idxs.emplace_back(plate_hover_id); + } else { + wxGetApp().plater()->get_partplate_list().reset_hover_id(); + } + m_gizmos.set_hover_id(-1); + break; + } + default: + { + assert(false); + break; + } + } } + else + m_gizmos.set_hover_id(-1); + + _update_volumes_hover_state(); + +#if ENABLE_RAYCAST_PICKING_DEBUG + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + std::string object_type = "None"; + switch (hit.type) + { + case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; } + case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; } + case SceneRaycaster::EType::FallbackGizmo: { object_type = "Gizmo2 element"; break; } + case SceneRaycaster::EType::Volume: + { + if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower) + object_type = "Volume (Wipe tower)"; + else if (m_volumes.volumes[hit.raycaster_id]->volume_idx() == -int(slaposPad)) + object_type = "Volume (SLA pad)"; + else if (m_volumes.volumes[hit.raycaster_id]->volume_idx() == -int(slaposSupportTree)) + object_type = "Volume (SLA supports)"; + else if (m_volumes.volumes[hit.raycaster_id]->is_modifier) + object_type = "Volume (Modifier)"; + else + object_type = "Volume (Part)"; + break; + } + default: { break; } + } + + auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color, + const std::string& col_3 = "", const ImVec4& col_3_color = ImGui::GetStyleColorVec4(ImGuiCol_Text)) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(col_1_color, col_1.c_str()); + ImGui::TableSetColumnIndex(1); + imgui.text_colored(col_2_color, col_2.c_str()); + if (!col_3.empty()) { + ImGui::TableSetColumnIndex(2); + imgui.text_colored(col_3_color, col_3.c_str()); + } + }; + + char buf[1024]; + if (hit.type != SceneRaycaster::EType::None) { + if (ImGui::BeginTable("Hit", 2)) { + add_strings_row_to_table("Object ID", ImGuiWrapper::COL_ORANGE_LIGHT, std::to_string(hit.raycaster_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Type", ImGuiWrapper::COL_ORANGE_LIGHT, object_type, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%.3f, %.3f, %.3f", hit.position.x(), hit.position.y(), hit.position.z()); + add_strings_row_to_table("Position", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%.3f, %.3f, %.3f", hit.normal.x(), hit.normal.y(), hit.normal.z()); + add_strings_row_to_table("Normal", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + } + else + imgui.text("NO HIT"); + + ImGui::Separator(); + imgui.text("Registered for picking:"); + if (ImGui::BeginTable("Raycasters", 2)) { + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.beds_count(), (int)m_scene_raycaster.active_beds_count()); + add_strings_row_to_table("Beds", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.volumes_count(), (int)m_scene_raycaster.active_volumes_count()); + add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count()); + add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.fallback_gizmos_count(), (int)m_scene_raycaster.active_fallback_gizmos_count()); + add_strings_row_to_table("Gizmo2 elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + + std::vector>* gizmo_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::Gizmo); + if (gizmo_raycasters != nullptr && !gizmo_raycasters->empty()) { + ImGui::Separator(); + imgui.text("Gizmo raycasters IDs:"); + if (ImGui::BeginTable("GizmoRaycasters", 3)) { + for (size_t i = 0; i < gizmo_raycasters->size(); ++i) { + add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT, + std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, (*gizmo_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text), + to_string(Geometry::Transformation((*gizmo_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + } + ImGui::EndTable(); + } + } + + std::vector>* gizmo2_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::FallbackGizmo); + if (gizmo2_raycasters != nullptr && !gizmo2_raycasters->empty()) { + ImGui::Separator(); + imgui.text("Gizmo2 raycasters IDs:"); + if (ImGui::BeginTable("Gizmo2Raycasters", 3)) { + for (size_t i = 0; i < gizmo2_raycasters->size(); ++i) { + add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT, + std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::FallbackGizmo, (*gizmo2_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text), + to_string(Geometry::Transformation((*gizmo2_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + } + ImGui::EndTable(); + } + } + + imgui.end(); +#endif // ENABLE_RAYCAST_PICKING_DEBUG } void GLCanvas3D::_rectangular_selection_picking_pass() @@ -6569,6 +6602,56 @@ void GLCanvas3D::_rectangular_selection_picking_pass() std::set idxs; if (m_picking_enabled) { + const size_t width = std::max(m_rectangle_selection.get_width(), 1); + const size_t height = std::max(m_rectangle_selection.get_height(), 1); + + const OpenGLManager::EFramebufferType framebuffers_type = OpenGLManager::get_framebuffers_type(); + bool use_framebuffer = framebuffers_type != OpenGLManager::EFramebufferType::Unknown; + + GLuint render_fbo = 0; + GLuint render_tex = 0; + GLuint render_depth = 0; + if (use_framebuffer) { + // setup a framebuffer which covers only the selection rectangle + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + glsafe(::glGenFramebuffers(1, &render_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); + } + else { + glsafe(::glGenFramebuffersEXT(1, &render_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); + } + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); + glsafe(::glGenRenderbuffers(1, &render_depth)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)); + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); + } + else { + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); + glsafe(::glGenRenderbuffersEXT(1, &render_depth)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); + glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height)); + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); + } + const GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + use_framebuffer = false; + } + else { + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) + use_framebuffer = false; + } + } + if (m_multisample_allowed) // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. glsafe(::glDisable(GL_MULTISAMPLE)); @@ -6578,20 +6661,48 @@ void GLCanvas3D::_rectangular_selection_picking_pass() glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - _render_volumes_for_picking(); + Camera& main_camera = wxGetApp().plater()->get_camera(); + Camera framebuffer_camera; + Camera* camera = &main_camera; + if (use_framebuffer) { + // setup a camera which covers only the selection rectangle + const std::array& viewport = camera->get_viewport(); + const double near_left = camera->get_near_left(); + const double near_bottom = camera->get_near_bottom(); + const double near_width = camera->get_near_width(); + const double near_height = camera->get_near_height(); + + const double ratio_x = near_width / double(viewport[2]); + const double ratio_y = near_height / double(viewport[3]); + + const double rect_near_left = near_left + double(m_rectangle_selection.get_left()) * ratio_x; + const double rect_near_bottom = near_bottom + (double(viewport[3]) - double(m_rectangle_selection.get_bottom())) * ratio_y; + double rect_near_right = near_left + double(m_rectangle_selection.get_right()) * ratio_x; + double rect_near_top = near_bottom + (double(viewport[3]) - double(m_rectangle_selection.get_top())) * ratio_y; + + if (rect_near_left == rect_near_right) + rect_near_right = rect_near_left + ratio_x; + if (rect_near_bottom == rect_near_top) + rect_near_top = rect_near_bottom + ratio_y; + + framebuffer_camera.look_at(camera->get_position(), camera->get_target(), camera->get_dir_up()); + framebuffer_camera.apply_projection(rect_near_left, rect_near_right, rect_near_bottom, rect_near_top, camera->get_near_z(), camera->get_far_z()); + framebuffer_camera.set_viewport(0, 0, width, height); + framebuffer_camera.apply_viewport(); + camera = &framebuffer_camera; + } + + _render_volumes_for_picking(*camera); //BBS: remove the bed picking logic //_render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); - int width = std::max((int)m_rectangle_selection.get_width(), 1); - int height = std::max((int)m_rectangle_selection.get_height(), 1); - int px_count = width * height; + const size_t px_count = width * height; - int left = (int)m_rectangle_selection.get_left(); - int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top(); - if (left >= 0 && top >= 0) { + const size_t left = use_framebuffer ? 0 : (size_t)m_rectangle_selection.get_left(); + const size_t top = use_framebuffer ? 0 : (size_t)get_canvas_size().get_height() - (size_t)m_rectangle_selection.get_top(); #define USE_PARALLEL 1 #if USE_PARALLEL struct Pixel @@ -6635,14 +6746,33 @@ void GLCanvas3D::_rectangular_selection_picking_pass() idxs.insert(volume_id); } #endif // USE_PARALLEL - } + if (camera != &main_camera) + main_camera.apply_viewport(); + + if (framebuffers_type == OpenGLManager::EFramebufferType::Arb) { + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); + if (render_depth != 0) + glsafe(::glDeleteRenderbuffers(1, &render_depth)); + if (render_fbo != 0) + glsafe(::glDeleteFramebuffers(1, &render_fbo)); + } + else if (framebuffers_type == OpenGLManager::EFramebufferType::Ext) { + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); + if (render_depth != 0) + glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); + if (render_fbo != 0) + glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); + } + + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); } m_hover_volume_idxs.assign(idxs.begin(), idxs.end()); _update_volumes_hover_state(); } -void GLCanvas3D::_render_background() const +void GLCanvas3D::_render_background() { bool use_error_color = false; if (wxGetApp().is_editor()) { @@ -6665,45 +6795,47 @@ void GLCanvas3D::_render_background() const } } - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // Draws a bottom to top gradient over the complete screen. glsafe(::glDisable(GL_DEPTH_TEST)); - ::glBegin(GL_QUADS); + ColorRGBA background_color = m_is_dark ? DEFAULT_BG_LIGHT_COLOR_DARK : DEFAULT_BG_LIGHT_COLOR; + ColorRGBA error_background_color = m_is_dark ? ERROR_BG_LIGHT_COLOR_DARK : ERROR_BG_LIGHT_COLOR; + const ColorRGBA bottom_color = use_error_color ? error_background_color : background_color; - float* background_color = m_is_dark ? DEFAULT_BG_LIGHT_COLOR_DARK : DEFAULT_BG_LIGHT_COLOR; - float* error_background_color = m_is_dark ? ERROR_BG_LIGHT_COLOR_DARK : ERROR_BG_LIGHT_COLOR; + if (!m_background.is_initialized()) { + m_background.reset(); - if (use_error_color) - ::glColor3fv(error_background_color); - else - ::glColor3fv(background_color); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); - ::glVertex2f(-1.0f, -1.0f); - ::glVertex2f(1.0f, -1.0f); + // vertices + init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, -1.0f), Vec2f(1.0f, 0.0f)); + init_data.add_vertex(Vec2f(1.0f, 1.0f), Vec2f(1.0f, 1.0f)); + init_data.add_vertex(Vec2f(-1.0f, 1.0f), Vec2f(0.0f, 1.0f)); - if (use_error_color) - ::glColor3fv(error_background_color); - else - ::glColor3fv(background_color); + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); - ::glVertex2f(1.0f, 1.0f); - ::glVertex2f(-1.0f, 1.0f); - glsafe(::glEnd()); + m_background.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("background"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("top_color", bottom_color); + shader->set_uniform("bottom_color", bottom_color); + m_background.render(); + shader->stop_using(); + } glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glPopMatrix()); - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glPopMatrix()); } -void GLCanvas3D::_render_bed(bool bottom, bool show_axes) +void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes) { float scale_factor = 1.0; #if ENABLE_RETINA_GL @@ -6721,27 +6853,12 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes) //bool show_texture = true; //BBS set axes mode m_bed.set_axes_mode(m_main_toolbar.is_enabled()); - m_bed.render(*this, bottom, scale_factor, show_axes); + m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes); } -void GLCanvas3D::_render_bed_for_picking(bool bottom) +void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) { - float scale_factor = 1.0; -#if ENABLE_RETINA_GL - scale_factor = m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - //m_bed.render_for_picking(*this, bottom, scale_factor); -} - -void GLCanvas3D::_render_platelist(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) const -{ - wxGetApp().plater()->get_partplate_list().render(bottom, only_current, only_body, hover_id, render_cali); -} - -void GLCanvas3D::_render_plates_for_picking() const -{ - wxGetApp().plater()->get_partplate_list().render_for_picking_pass(); + wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali); } void GLCanvas3D::_render_plane() const @@ -6824,15 +6941,16 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with default: case GLVolumeCollection::ERenderType::Opaque: { - const GLGizmosManager& gm = get_gizmos_manager(); + GLGizmosManager& gm = get_gizmos_manager(); if (dynamic_cast(gm.get_current()) == nullptr) { if (m_picking_enabled && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { int object_id = m_layers_editing.last_object_id; - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); m_layers_editing.render_volumes(*this, m_volumes); } else { @@ -6844,7 +6962,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with }*/ //BBS:add assemble view related logic // do not cull backfaces to show broken geometry, if any - m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this, canvas_type](const GLVolume& volume) { + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, m_picking_enabled, camera.get_view_matrix(), camera.get_projection_matrix(), [this, canvas_type](const GLVolume& volume) { if (canvas_type == ECanvasType::CanvasAssembleView) { return !volume.is_modifier && !volume.is_wipe_tower; } @@ -6876,8 +6995,9 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with else shader->set_uniform("show_wireframe", false); }*/ + const Camera& camera = wxGetApp().plater()->get_camera(); //BBS:add assemble view related logic - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [this, canvas_type](const GLVolume& volume) { + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [this, canvas_type](const GLVolume& volume) { if (canvas_type == ECanvasType::CanvasAssembleView) { return !volume.is_modifier; } @@ -6937,7 +7057,7 @@ void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) } } -void GLCanvas3D::_render_selection() const +void GLCanvas3D::_render_selection() { float scale_factor = 1.0; #if ENABLE_RETINA_GL @@ -6957,8 +7077,8 @@ void GLCanvas3D::_render_sequential_clearance() { case GLGizmosManager::EType::Flatten: case GLGizmosManager::EType::Cut: - case GLGizmosManager::EType::Hollow: - case GLGizmosManager::EType::SlaSupports: + // case GLGizmosManager::EType::Hollow: + // case GLGizmosManager::EType::SlaSupports: case GLGizmosManager::EType::FdmSupports: case GLGizmosManager::EType::Seam: { return; } default: { break; } @@ -6968,7 +7088,7 @@ void GLCanvas3D::_render_sequential_clearance() } #if ENABLE_RENDER_SELECTION_CENTER -void GLCanvas3D::_render_selection_center() const +void GLCanvas3D::_render_selection_center() { m_selection.render_center(m_gizmos.is_dragging()); } @@ -7032,9 +7152,6 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars // calculate scale needed for items in all top toolbars -#ifdef __WINDOWS__ - cnv_size.set_width(cnv_size.get_width() + m_separator_toolbar.get_width() + collapse_toolbar_width); -#endif float new_h_scale = (cnv_size.get_width() - noitems_width) / (items_cnt * GLToolbar::Default_Icons_Size); //for protect @@ -7071,14 +7188,6 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() void GLCanvas3D::_render_overlays() { glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the textures are renderered inside the frustrum - const Camera& camera = wxGetApp().plater()->get_camera(); - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.10))); - // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); _check_and_update_toolbar_icon_scale(); @@ -7150,8 +7259,6 @@ void GLCanvas3D::_render_overlays() }*/ } m_labels.render(sorted_instances); - - glsafe(::glPopMatrix()); } void GLCanvas3D::_render_style_editor() @@ -7247,17 +7354,16 @@ void GLCanvas3D::_render_style_editor() ImGui::End(); } -void GLCanvas3D::_render_volumes_for_picking() const +void GLCanvas3D::_render_volumes_for_picking(const Camera& camera) const { - static const GLfloat INV_255 = 1.0f / 255.0f; + GLShaderProgram* shader = wxGetApp().get_shader("flat_clip"); + if (shader == nullptr) + return; // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix(); + const Transform3d& view_matrix = camera.get_view_matrix(); for (size_t type = 0; type < 2; ++ type) { GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::ERenderType::Opaque : GLVolumeCollection::ERenderType::Transparent, view_matrix); for (const GLVolumeWithIdAndZ& volume : to_render) @@ -7266,20 +7372,22 @@ void GLCanvas3D::_render_volumes_for_picking() const // we reserve color = (0,0,0) for occluders (as the printbed) // so we shift volumes' id by 1 to get the proper color //BBS: remove the bed picking logic - unsigned int id = volume.second.first; - //unsigned int id = 1 + volume.second.first; - unsigned int r = (id & (0x000000FF << 0)) << 0; - unsigned int g = (id & (0x000000FF << 8)) >> 8; - unsigned int b = (id & (0x000000FF << 16)) >> 16; - unsigned int a = picking_checksum_alpha_channel(r, g, b); - glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); - volume.first->render(); + const unsigned int id = volume.second.first; + //const unsigned int id = 1 + volume.second.first; + volume.first->model.set_color(picking_decode(id)); + shader->start_using(); + shader->set_uniform("view_model_matrix", view_matrix * volume.first->world_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); + shader->set_uniform("z_range", m_volumes.get_z_range()); + shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane()); + volume.first->picking = true; + volume.first->render(); + volume.first->picking = false; + shader->stop_using(); } } - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnable(GL_CULL_FACE)); } @@ -7319,32 +7427,19 @@ void GLCanvas3D::_render_main_toolbar() if (!m_main_toolbar.is_enabled()) return; - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + const Size cnv_size = get_canvas_size(); + const float top = 0.5f * (float)cnv_size.get_height(); -#if BBS_TOOLBAR_ON_TOP GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; - float gizmo_width = m_gizmos.get_scaled_total_width(); - float assemble_width = m_assemble_view_toolbar.get_width(); - float separator_width = m_separator_toolbar.get_width(); - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + separator_width + gizmo_width + assemble_width - collapse_toolbar_width)) * inv_zoom; -#else - float gizmo_height = m_gizmos.get_scaled_total_height(); - float space_height = GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale(); - float main_toolbar_height = (float)m_main_toolbar.get_height(); - float assemble_height = m_assemble_view_toolbar.get_height(); - float top = 0.5f * (main_toolbar_height + gizmo_height + assemble_height) * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - m_main_toolbar.get_width()) * inv_zoom; - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": top %1%, main_toolbar_height %2%, space_height %3% gizmo_height %4%") % top % main_toolbar_height % space_height % gizmo_height; -#endif + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; + const float gizmo_width = m_gizmos.get_scaled_total_width(); + const float assemble_width = m_assemble_view_toolbar.get_width(); + const float separator_width = m_separator_toolbar.get_width(); + const float left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + separator_width + gizmo_width + assemble_width - collapse_toolbar_width)); m_main_toolbar.set_position(top, left); m_main_toolbar.render(*this); if (m_toolbar_highlighter.m_render_arrow) - { m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item); - } } //BBS: GUI refactor: GLToolbar adjust @@ -7669,28 +7764,16 @@ void GLCanvas3D::_render_assemble_view_toolbar() const if (!m_assemble_view_toolbar.is_enabled()) return; - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - -#if BBS_TOOLBAR_ON_TOP + const Size cnv_size = get_canvas_size(); GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; - float gizmo_width = m_gizmos.get_scaled_total_width(); - float assemble_width = m_assemble_view_toolbar.get_width(); - float separator_width = m_separator_toolbar.get_width(); - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width - separator_width - collapse_toolbar_width)) * inv_zoom; - float left = main_toolbar_left + (m_main_toolbar.get_width() + gizmo_width) * inv_zoom; - //float left = 0.5f * (m_main_toolbar.get_width() + gizmo_width - m_assemble_view_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; -#else - float gizmo_height = m_gizmos.get_scaled_total_height(); - //float space_height = GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale(); - float main_toolbar_height = (float)m_main_toolbar.get_height(); - float assemble_height = (float)m_assemble_view_toolbar.get_height(); - float top = 0.5f * (assemble_height - main_toolbar_height - gizmo_height) * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - m_assemble_view_toolbar.get_width()) * inv_zoom; - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": top %1%, main_toolbar_height %2%, space_height %3% gizmo_height %4%") % top % main_toolbar_height % space_height % gizmo_height; -#endif + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; + const float gizmo_width = m_gizmos.get_scaled_total_width(); + const float assemble_width = m_assemble_view_toolbar.get_width(); + const float separator_width = m_separator_toolbar.get_width(); + const float top = 0.5f * (float)cnv_size.get_height(); + const float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width + separator_width - collapse_toolbar_width)); + const float left = main_toolbar_left + (m_main_toolbar.get_width() + gizmo_width + separator_width); + m_assemble_view_toolbar.set_position(top, left); m_assemble_view_toolbar.render(*this); } @@ -7755,17 +7838,15 @@ void GLCanvas3D::_render_separator_toolbar_right() const if (!m_separator_toolbar.is_enabled()) return; - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - + const Size cnv_size = get_canvas_size(); GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; - float gizmo_width = m_gizmos.get_scaled_total_width(); - float assemble_width = m_assemble_view_toolbar.get_width(); - float separator_width = m_separator_toolbar.get_width(); - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width - collapse_toolbar_width)) * inv_zoom; - float left = main_toolbar_left + (m_main_toolbar.get_width() + gizmo_width) * inv_zoom; + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; + const float gizmo_width = m_gizmos.get_scaled_total_width(); + const float assemble_width = m_assemble_view_toolbar.get_width(); + const float separator_width = m_separator_toolbar.get_width(); + const float top = 0.5f * (float)cnv_size.get_height(); + const float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width + separator_width - collapse_toolbar_width)); + const float left = main_toolbar_left + (m_main_toolbar.get_width() + gizmo_width + separator_width / 2); m_separator_toolbar.set_position(top, left); m_separator_toolbar.render(*this,GLToolbarItem::SeparatorLine); @@ -7776,17 +7857,15 @@ void GLCanvas3D::_render_separator_toolbar_left() const if (!m_separator_toolbar.is_enabled()) return; - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - + const Size cnv_size = get_canvas_size(); GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; - float gizmo_width = m_gizmos.get_scaled_total_width(); - float assemble_width = m_assemble_view_toolbar.get_width(); - float separator_width = m_separator_toolbar.get_width(); - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width + separator_width - collapse_toolbar_width)) * inv_zoom; - float left = main_toolbar_left + (m_main_toolbar.get_width()) * inv_zoom; + const float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; + const float gizmo_width = m_gizmos.get_scaled_total_width(); + const float assemble_width = m_assemble_view_toolbar.get_width(); + const float separator_width = m_separator_toolbar.get_width(); + const float top = 0.5f * (float)cnv_size.get_height(); + const float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width + separator_width - collapse_toolbar_width)); + const float left = main_toolbar_left + (m_main_toolbar.get_width()); m_separator_toolbar.set_position(top, left); m_separator_toolbar.render(*this,GLToolbarItem::SeparatorLine); @@ -7796,12 +7875,10 @@ void GLCanvas3D::_render_collapse_toolbar() const { GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); - Size cnv_size = get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - - float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - //float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; + const Size cnv_size = get_canvas_size(); + const float top = 0.5f * (float)cnv_size.get_height(); + //const float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band); + const float left = -0.5f * (float)cnv_size.get_width(); collapse_toolbar.set_position(top, left); collapse_toolbar.render(*this); @@ -7870,15 +7947,15 @@ void GLCanvas3D::_render_paint_toolbar() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImGuiContext& context = *GImGui; bool disabled = !wxGetApp().plater()->can_fillcolor(); - unsigned char rgb[3]; + ColorRGBA rgba; for (int i = 0; i < extruder_num; i++) { if (i > 0) ImGui::SameLine(); - Slic3r::GUI::BitmapCache::parse_color(colors[i], rgb); - ImGui::PushStyleColor(ImGuiCol_Button, ImColor(rgb[0], rgb[1], rgb[2]).Value); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImColor(rgb[0], rgb[1], rgb[2]).Value); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImColor(rgb[0], rgb[1], rgb[2]).Value); + decode_color(colors[i], rgba); + ImGui::PushStyleColor(ImGuiCol_Button, ImGuiWrapper::to_ImVec4(rgba)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGuiWrapper::to_ImVec4(rgba)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGuiWrapper::to_ImVec4(rgba)); if (disabled) ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); if (ImGui::Button(("##filament_button" + std::to_string(i)).c_str(), button_size)) { @@ -7899,9 +7976,9 @@ void GLCanvas3D::_render_paint_toolbar() const } const float text_offset_y = 4.0f * em_unit * f_scale; - for (int i = 0; i < extruder_num; i++){ - Slic3r::GUI::BitmapCache::parse_color(colors[i], rgb); - float gray = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]; + for (int i = 0; i < extruder_num; i++) { + decode_color(colors[i], rgba); + float gray = 0.299 * rgba.r_uchar() + 0.587 * rgba.g_uchar() + 0.114 * rgba.b_uchar(); ImVec4 text_color = gray < 80 ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0, 0, 0, 1.0f); imgui.push_bold_font(); @@ -8078,28 +8155,58 @@ void GLCanvas3D::_render_assemble_info() const } #if ENABLE_SHOW_CAMERA_TARGET -void GLCanvas3D::_render_camera_target() const +void GLCanvas3D::_render_camera_target() { - double half_length = 5.0; + static const float half_length = 5.0f; glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(2.0f)); - ::glBegin(GL_LINES); - const Vec3d& target = wxGetApp().plater()->get_camera().get_target(); - // draw line for x axis - ::glColor3f(1.0f, 0.0f, 0.0f); - ::glVertex3d(target(0) - half_length, target(1), target(2)); - ::glVertex3d(target(0) + half_length, target(1), target(2)); - // draw line for y axis - ::glColor3f(0.0f, 1.0f, 0.0f); - ::glVertex3d(target(0), target(1) - half_length, target(2)); - ::glVertex3d(target(0), target(1) + half_length, target(2)); - // draw line for z axis - ::glColor3f(0.0f, 0.0f, 1.0f); - ::glVertex3d(target(0), target(1), target(2) - half_length); - ::glVertex3d(target(0), target(1), target(2) + half_length); - glsafe(::glEnd()); + const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast(); + bool target_changed = !m_camera_target.target.isApprox(target.cast()); + m_camera_target.target = target.cast(); + + for (int i = 0; i < 3; ++i) { + if (!m_camera_target.axis[i].is_initialized() || target_changed) { + m_camera_target.axis[i].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z()); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + if (i == X) { + init_data.add_vertex(Vec3f(target.x() - half_length, target.y(), target.z())); + init_data.add_vertex(Vec3f(target.x() + half_length, target.y(), target.z())); + } + else if (i == Y) { + init_data.add_vertex(Vec3f(target.x(), target.y() - half_length, target.z())); + init_data.add_vertex(Vec3f(target.x(), target.y() + half_length, target.z())); + } + else { + init_data.add_vertex(Vec3f(target.x(), target.y(), target.z() - half_length)); + init_data.add_vertex(Vec3f(target.x(), target.y(), target.z() + half_length)); + } + + // indices + init_data.add_line(0, 1); + + m_camera_target.axis[i].init_from(std::move(init_data)); + } + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + for (int i = 0; i < 3; ++i) { + m_camera_target.axis[i].render(); + } + shader->stop_using(); + } } #endif // ENABLE_SHOW_CAMERA_TARGET @@ -8122,32 +8229,51 @@ void GLCanvas3D::_render_sla_slices() if (!obj->is_step_done(slaposSliceSupports)) continue; - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); + SlaCap::ObjectIdToModelsMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToModelsMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); { if (it_caps_bottom == m_sla_caps[0].triangles.end()) it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; if (!m_sla_caps[0].matches(clip_min_z)) { m_sla_caps[0].z = clip_min_z; - it_caps_bottom->second.object.clear(); - it_caps_bottom->second.supports.clear(); + it_caps_bottom->second.object.reset(); + it_caps_bottom->second.supports.reset(); } if (it_caps_top == m_sla_caps[1].triangles.end()) it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; if (!m_sla_caps[1].matches(clip_max_z)) { m_sla_caps[1].z = clip_max_z; - it_caps_top->second.object.clear(); - it_caps_top->second.supports.clear(); + it_caps_top->second.object.reset(); + it_caps_top->second.supports.reset(); } } - Pointf3s &bottom_obj_triangles = it_caps_bottom->second.object; - Pointf3s &bottom_sup_triangles = it_caps_bottom->second.supports; - Pointf3s &top_obj_triangles = it_caps_top->second.object; - Pointf3s &top_sup_triangles = it_caps_top->second.supports; + GLModel& bottom_obj_triangles = it_caps_bottom->second.object; + GLModel& bottom_sup_triangles = it_caps_bottom->second.supports; + GLModel& top_obj_triangles = it_caps_top->second.object; + GLModel& top_sup_triangles = it_caps_top->second.supports; - if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && - !obj->get_slice_index().empty()) - { + auto init_model = [](GLModel& model, const Pointf3s& triangles, const ColorRGBA& color) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(triangles.size()); + init_data.reserve_indices(triangles.size() / 3); + init_data.color = color; + + unsigned int vertices_count = 0; + for (const Vec3d& v : triangles) { + init_data.add_vertex((Vec3f)v.cast()); + ++vertices_count; + if (vertices_count % 3 == 0) { + init_data.add_triangle(vertices_count - 3, vertices_count - 2, vertices_count - 1); + } + } + + if (!init_data.is_empty()) + model.init_from(std::move(init_data)); + }; + + if ((!bottom_obj_triangles.is_initialized() || !bottom_sup_triangles.is_initialized() || + !top_obj_triangles.is_initialized() || !top_sup_triangles.is_initialized()) && !obj->get_slice_index().empty()) { double layer_height = print->default_object_config().layer_height.value; double initial_layer_height = print->material_config().initial_layer_height.value; bool left_handed = obj->is_left_handed(); @@ -8168,60 +8294,54 @@ void GLCanvas3D::_render_sla_slices() const ExPolygons& obj_bottom = slice_low.get_slice(soModel); const ExPolygons& sup_bottom = slice_low.get_slice(soSupport); // calculate model bottom cap - if (bottom_obj_triangles.empty() && !obj_bottom.empty()) - bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed); + // calculate model bottom cap + if (!bottom_obj_triangles.is_initialized() && !obj_bottom.empty()) + init_model(bottom_obj_triangles, triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); // calculate support bottom cap - if (bottom_sup_triangles.empty() && !sup_bottom.empty()) - bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, ! left_handed); + if (!bottom_sup_triangles.is_initialized() && !sup_bottom.empty()) + init_model(bottom_sup_triangles, triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); } if (slice_high.is_valid()) { const ExPolygons& obj_top = slice_high.get_slice(soModel); const ExPolygons& sup_top = slice_high.get_slice(soSupport); // calculate model top cap - if (top_obj_triangles.empty() && !obj_top.empty()) - top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed); + // calculate model top cap + if (!top_obj_triangles.is_initialized() && !obj_top.empty()) + init_model(top_obj_triangles, triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); // calculate support top cap - if (top_sup_triangles.empty() && !sup_top.empty()) - top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed); + if (!top_sup_triangles.is_initialized() && !sup_top.empty()) + init_model(top_sup_triangles, triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); } } - if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { - for (const SLAPrintObject::Instance& inst : obj->instances()) { - glsafe(::glPushMatrix()); - glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0)); - glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0, 0.0, 1.0)); - if (obj->is_left_handed()) - // The polygons are mirrored by X. - glsafe(::glScalef(-1.0, 1.0, 1.0)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); - if (!bottom_obj_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size())); - } - if (! top_obj_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size())); - } - glsafe(::glColor3f(1.0f, 0.0f, 0.37f)); - if (! bottom_sup_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size())); - } - if (! top_sup_triangles.empty()) { - glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size())); - } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glPopMatrix()); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + for (const SLAPrintObject::Instance& inst : obj->instances()) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(Vec3d(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0), + inst.rotation * Vec3d::UnitZ(), Vec3d::Ones(), + obj->is_left_handed() ? Vec3d(-1.0f, 1.0f, 1.0f) : Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + bottom_obj_triangles.render(); + top_obj_triangles.render(); + bottom_sup_triangles.render(); + top_sup_triangles.render(); + } + + shader->stop_using(); } } } -void GLCanvas3D::_render_selection_sidebar_hints() const +void GLCanvas3D::_render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field, m_gizmos.get_uniform_scaling()); } @@ -8331,21 +8451,17 @@ Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) if (m_canvas == nullptr) return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); - const Camera& camera = wxGetApp().plater()->get_camera(); - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - GLint y = viewport[3] - (GLint)mouse_pos(1); - GLfloat mouse_z; - if (z == nullptr) - glsafe(::glReadPixels((GLint)mouse_pos(0), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z)); - else - mouse_z = *z; - - Vec3d out; - igl::unproject(Vec3d(mouse_pos(0), y, mouse_z), modelview, projection, viewport, out); - return out; + if (z == nullptr) { + const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(mouse_pos.cast(), wxGetApp().plater()->get_camera(), nullptr); + return hit.is_valid() ? hit.position.cast() : _mouse_to_bed_3d(mouse_pos); + } + else { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Vec4i viewport(camera.get_viewport().data()); + Vec3d out; + igl::unproject(Vec3d(mouse_pos.x(), viewport[3] - mouse_pos.y(), *z), camera.get_view_matrix().matrix(), camera.get_projection_matrix().matrix(), viewport, out); + return out; + } } Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) @@ -8375,7 +8491,7 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) if (!print->has_skirt() && !print->has_brim()) return; - const std::array color = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish + const ColorRGBA color = ColorRGBA::GREENISH(); // number of skirt layers size_t total_layer_count = 0; @@ -8401,27 +8517,29 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) skirt_height = std::min(skirt_height, print_zs.size()); print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); - GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE); + GLVolume* volume = m_volumes.new_toolpath_volume(color); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; for (size_t i = 0; i < skirt_height; ++ i) { volume->print_zs.emplace_back(print_zs[i]); - volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size()); - volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size()); + volume->offsets.emplace_back(init_data.indices_count()); //BBS: usage of m_brim are deleted - _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume); + _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - if (volume->indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { - GLVolume &vol = *volume; + if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + volume->model.init_from(std::move(init_data)); + GLVolume &vol = *volume; volume = m_volumes.new_toolpath_volume(vol.color); - reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); } } - volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array.vertices_and_normals_interleaved, volume->indexed_vertex_array.bounding_box()); - volume->indexed_vertex_array.finalize_geometry(m_initialized); + volume->model.init_from(std::move(init_data)); + volume->is_outside = !contains(build_volume, volume->model); } void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) { - std::vector> tool_colors = _parse_colors(str_tool_colors); + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { @@ -8430,20 +8548,20 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool has_perimeters; bool has_infill; bool has_support; - const std::vector>* tool_colors; + const std::vector* tool_colors; bool is_single_material_print; int filaments_cnt; const std::vector* color_print_values; - static const std::array& color_perimeters() { static std::array color = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const std::array& color_infill() { static std::array color = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - static const std::array& color_pause_or_custom_code() { static std::array color = { 0.5f, 0.5f, 0.5f, 1.f }; return color; } // gray + static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } + static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } + static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } - const std::array& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } // For coloring by a color_print(M600), return a parsed color. bool color_by_color_print() const { return color_print_values!=nullptr; } @@ -8583,11 +8701,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const std::array& color) { + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { // Allocate the volume before locking. GLVolume *volume = new GLVolume(color); volume->is_extrusion_path = true; - tbb::spin_mutex::scoped_lock lock; + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); + tbb::spin_mutex::scoped_lock lock; // Lock by ROII, so if the emplace_back() fails, the lock will be released. lock.acquire(new_volume_mutex); m_volumes.volumes.emplace_back(volume); @@ -8599,31 +8720,36 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { GLVolumePtrs vols; - auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& { - return *vols[ctxt.color_by_color_print()? + std::vector geometries; + auto select_geometry = [&ctxt, &geometries](size_t layer_idx, int extruder, int feature) -> GLModel::Geometry& { + return geometries[ctxt.color_by_color_print() ? ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : - ctxt.color_by_tool() ? - std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : - feature - ]; + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; }; if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) + for (size_t i = 0; i < ctxt.number_tools(); ++i) { vols.emplace_back(new_volume(ctxt.color_tool(i))); + geometries.emplace_back(GLModel::Geometry()); + } } - else + else { vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; - for (GLVolume *vol : vols) - // Reserving number of vertices (3x position + 3x color) - vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); + geometries = { GLModel::Geometry(), GLModel::Geometry(), GLModel::Geometry() }; + } + + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + } for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer *layer = ctxt.layers[idx_layer]; - if (is_selected_separate_extruder) - { + if (is_selected_separate_extruder) { bool at_least_one_has_correct_extruder = false; - for (const LayerRegion* layerm : layer->regions()) - { + for (const LayerRegion* layerm : layer->regions()) { if (layerm->slices.surfaces.empty()) continue; const PrintRegionConfig& cfg = layerm->region().config(); @@ -8638,12 +8764,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c continue; } - for (GLVolume *vol : vols) + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume* vol = vols[i]; if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { vol->print_zs.emplace_back(layer->print_z); - vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size()); - vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size()); + vol->offsets.emplace_back(geometries[i].indices_count()); } + } + for (const PrintInstance &instance : *ctxt.shifted_copies) { const Point © = instance.shift; for (const LayerRegion *layerm : layer->regions()) { @@ -8657,18 +8785,16 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } if (ctxt.has_perimeters) _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - volume(idx_layer, layerm->region().config().wall_filament.value, 0)); + select_geometry(idx_layer, layerm->region().config().wall_filament.value, 0)); if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); if (! fill->entities.empty()) _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - volume(idx_layer, - is_solid_infill(fill->entities.front()->role()) ? - layerm->region().config().solid_infill_filament : - layerm->region().config().sparse_infill_filament, - 1)); + select_geometry(idx_layer, is_solid_infill(fill->entities.front()->role()) ? + layerm->region().config().solid_infill_filament : + layerm->region().config().sparse_infill_filament, 1)); } } } @@ -8677,40 +8803,42 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c if (support_layer) { for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - volume(idx_layer, - (extrusion_entity->role() == erSupportMaterial || - extrusion_entity->role() == erSupportTransition) ? - support_layer->object()->config().support_filament : - support_layer->object()->config().support_interface_filament, - 2)); + select_geometry(idx_layer, (extrusion_entity->role() == erSupportMaterial || extrusion_entity->role() == erSupportTransition) ? + support_layer->object()->config().support_filament : + support_layer->object()->config().support_interface_filament, 2)); } } } // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. for (size_t i = 0; i < vols.size(); ++i) { GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { - vols[i] = new_volume(vol.color); - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); - } + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); + vols[i] = new_volume(vol.color); + } } } - for (GLVolume *vol : vols) - // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, - // but this code runs in parallel and the OpenGL driver is not thread safe. - vol->indexed_vertex_array.shrink_to_fit(); + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } }); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; - v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); - v->indexed_vertex_array.finalize_geometry(m_initialized); + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); } BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); @@ -8725,21 +8853,22 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con if (!print->is_step_done(psWipeTower)) return; - std::vector> tool_colors = _parse_colors(str_tool_colors); + std::vector tool_colors; + decode_colors(str_tool_colors, tool_colors); struct Ctxt { - const Print *print; - const std::vector>* tool_colors; - Vec2f wipe_tower_pos; - float wipe_tower_angle; + const Print *print; + const std::vector *tool_colors; + Vec2f wipe_tower_pos; + float wipe_tower_angle; - static const std::array& color_support() { static std::array color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } - const std::array& color_tool(size_t tool) const { return (*tool_colors)[tool]; } + const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } int volume_idx(int tool, int feature) const { return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; } @@ -8779,9 +8908,12 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); size_t grain_size = std::max(n_items / 128, size_t(1)); tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const std::array& color) { + auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { auto *volume = new GLVolume(color); volume->is_extrusion_path = true; + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); tbb::spin_mutex::scoped_lock lock; lock.acquire(new_volume_mutex); m_volumes.volumes.emplace_back(volume); @@ -8795,23 +8927,29 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con [&ctxt, &new_volume](const tbb::blocked_range& range) { // Bounding box of this slab of a wipe tower. GLVolumePtrs vols; + std::vector geometries; if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) + for (size_t i = 0; i < ctxt.number_tools(); ++i) { vols.emplace_back(new_volume(ctxt.color_tool(i))); + geometries.emplace_back(GLModel::Geometry()); + } } - else + else { vols = { new_volume(ctxt.color_support()) }; - for (GLVolume *volume : vols) - // Reserving number of vertices (3x position + 3x color) - volume->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); + geometries = { GLModel::Geometry() }; + } + + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + } for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { const std::vector &layer = ctxt.tool_change(idx_layer); for (size_t i = 0; i < vols.size(); ++i) { GLVolume &vol = *vols[i]; if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { vol.print_zs.emplace_back(layer.front().print_z); - vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size()); + vol.offsets.emplace_back(geometries[i].indices_count()); } } for (const WipeTower::ToolChangeResult &extrusions : layer) { @@ -8854,31 +8992,38 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con e_prev = e; } _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - *vols[ctxt.volume_idx(e.tool, 0)]); + geometries[ctxt.volume_idx(e.tool, 0)]); } } } for (size_t i = 0; i < vols.size(); ++i) { GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); vols[i] = new_volume(vol.color); - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); } } - for (GLVolume *vol : vols) - vol->indexed_vertex_array.shrink_to_fit(); - }); + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } + }); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); + { + for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) + if ((*ptr_it)->empty()) { + delete *ptr_it; + *ptr_it = nullptr; + } + m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); + } for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; - v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); - v->indexed_vertex_array.finalize_geometry(m_initialized); + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); } BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); @@ -8898,15 +9043,14 @@ void GLCanvas3D::_load_sla_shells() return; auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, - const TriangleMesh& mesh, const std::array& color, bool outside_printer_detection_enabled) { + const TriangleMesh& mesh, const ColorRGBA& color, bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); #if ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.load_mesh(mesh, true); + v.model.init_from(mesh, true); #else - v.indexed_vertex_array.load_mesh(mesh); + v.model.init_from(mesh); #endif // ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.finalize_geometry(m_initialized); v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0)); @@ -8970,28 +9114,6 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) _set_warning_notification(warning, show); } -std::vector> GLCanvas3D::_parse_colors(const std::vector& colors) -{ - static const float INV_255 = 1.0f / 255.0f; - - std::vector> output(colors.size(), { 1.0f, 1.0f, 1.0f, 1.0f }); - for (size_t i = 0; i < colors.size(); ++i) { - const std::string& color = colors[i]; - const char* c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if (digit1 == -1 || digit2 == -1) - break; - - output[i][j] = float(digit1 * 16 + digit2) * INV_255; - } - } - } - return output; -} - void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) { enum ErrorType{ diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index fb1780c7bc..523b0d5f6e 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1,3 +1,8 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, David Kocík @kocikdav, Vojtěch Král @vojtechkral +///|/ Copyright (c) BambuStudio 2023 manch1n @manch1n +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLCanvas3D_hpp_ #define slic3r_GLCanvas3D_hpp_ @@ -16,6 +21,7 @@ #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GCodeViewer.hpp" #include "Camera.hpp" +#include "SceneRaycaster.hpp" #include "IMToolbar.hpp" #include "libslic3r/Slicing.hpp" @@ -62,25 +68,24 @@ class RetinaHelper; class Size { - int m_width; - int m_height; - float m_scale_factor; + int m_width{ 0 }; + int m_height{ 0 }; + float m_scale_factor{ 1.0f }; public: - Size(); - Size(int width, int height, float scale_factor = 1.0); + Size() = default; + Size(int width, int height, float scale_factor = 1.0f) : m_width(width), m_height(height), m_scale_factor(scale_factor) {} - int get_width() const; - void set_width(int width); + int get_width() const { return m_width; } + void set_width(int width) { m_width = width; } - int get_height() const; - void set_height(int height); + int get_height() const { return m_height; } + void set_height(int height) { m_height = height; } - int get_scale_factor() const; - void set_scale_factor(int height); + float get_scale_factor() const { return m_scale_factor; } + void set_scale_factor(float factor) { m_scale_factor = factor; } }; - class RenderTimerEvent : public wxEvent { public: @@ -199,14 +204,6 @@ class GLCanvas3D static const double DefaultCameraZoomToBedMarginFactor; static const double DefaultCameraZoomToPlateMarginFactor; - - static float DEFAULT_BG_LIGHT_COLOR[3]; - static float ERROR_BG_LIGHT_COLOR[3]; - static float DEFAULT_BG_LIGHT_COLOR_LIGHT[3]; - static float ERROR_BG_LIGHT_COLOR_LIGHT[3]; - static float DEFAULT_BG_LIGHT_COLOR_DARK[3]; - static float ERROR_BG_LIGHT_COLOR_DARK[3]; - static void update_render_colors(); static void load_render_colors(); @@ -265,6 +262,15 @@ class GLCanvas3D int last_object_id{ -1 }; float last_z{ 0.0f }; LayerHeightEditActionType last_action{ LAYER_HEIGHT_EDIT_ACTION_INCREASE }; + struct Profile + { + GLModel baseline; + GLModel profile; + GLModel background; + float old_canvas_width{ 0.0f }; + std::vector old_layer_height_profile; + }; + Profile m_profile; LayersEditing() = default; ~LayersEditing(); @@ -293,7 +299,6 @@ class GLCanvas3D static float get_cursor_z_relative(const GLCanvas3D& canvas); static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); static Rect get_bar_rect_screen(const GLCanvas3D& canvas); - static Rect get_bar_rect_viewport(const GLCanvas3D& canvas); static float get_overlay_window_width() { return LayersEditing::s_overlay_window_width; } float object_max_z() const { return m_object_max_z; } @@ -303,10 +308,8 @@ class GLCanvas3D private: bool is_initialized() const; void generate_layer_height_texture(); - - void render_background_texture(const GLCanvas3D& canvas, const Rect& bar_rect); - void render_curve(const Rect& bar_rect); - + void render_active_object_annotations(const GLCanvas3D& canvas); + void render_profile(const GLCanvas3D& canvas); void update_slicing_parameters(); static float thickness_bar_width(const GLCanvas3D& canvas); @@ -356,12 +359,12 @@ class GLCanvas3D { struct Triangles { - Pointf3s object; - Pointf3s supports; + GLModel object; + GLModel supports; }; - typedef std::map ObjectIdToTrianglesMap; + typedef std::map ObjectIdToModelsMap; double z; - ObjectIdToTrianglesMap triangles; + ObjectIdToModelsMap triangles; SlaCap() { reset(); } void reset() { z = DBL_MAX; triangles.clear(); } @@ -504,6 +507,7 @@ private: bool m_is_dark = false; wxGLCanvas* m_canvas; wxGLContext* m_context; + SceneRaycaster m_scene_raycaster; Bed3D &m_bed; #if ENABLE_RETINA_GL std::unique_ptr m_retina_helper; @@ -527,7 +531,7 @@ private: std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; - SlaCap m_sla_caps[2]; + std::array m_sla_caps; std::string m_sidebar_field; // when true renders an extra frame by not resetting m_dirty to false // see request_extra_frame() @@ -578,14 +582,8 @@ private: // I just don't want to do it now before a release (Lukas Matena 24.3.2019) bool m_render_sla_auxiliaries; - std::string m_color_by; - bool m_reload_delayed; -#if ENABLE_RENDER_PICKING_PASS - bool m_show_picking_texture; -#endif // ENABLE_RENDER_PICKING_PASS - RenderStats m_render_stats; int m_imgui_undo_redo_hovered_pos{ -1 }; @@ -706,6 +704,16 @@ public: } m_gizmo_highlighter; +#if ENABLE_SHOW_CAMERA_TARGET + struct CameraTarget + { + std::array axis; + Vec3d target{ Vec3d::Zero() }; + }; + + CameraTarget m_camera_target; +#endif // ENABLE_SHOW_CAMERA_TARGET + GLModel m_background; public: explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed); ~GLCanvas3D(); @@ -722,6 +730,25 @@ public: bool init(); void post_event(wxEvent &&event); + std::shared_ptr add_raycaster_for_picking(SceneRaycaster::EType type, int id, const MeshRaycaster& raycaster, + const Transform3d& trafo = Transform3d::Identity(), bool use_back_faces = false) { + return m_scene_raycaster.add_raycaster(type, id, raycaster, trafo, use_back_faces); + } + void remove_raycasters_for_picking(SceneRaycaster::EType type, int id) { + m_scene_raycaster.remove_raycasters(type, id); + } + void remove_raycasters_for_picking(SceneRaycaster::EType type) { + m_scene_raycaster.remove_raycasters(type); + } + + std::vector>* get_raycasters_for_picking(SceneRaycaster::EType type) { + return m_scene_raycaster.get_raycasters(type); + } + + void set_raycaster_gizmos_on_top(bool value) { + m_scene_raycaster.set_gizmos_on_top(value); + } + void reset_explosion_ratio() { m_explosion_ratio = 1.0; } void on_change_color_mode(bool is_dark, bool reinit = true); const bool get_dark_mode_status() { return m_is_dark; } @@ -780,7 +807,9 @@ public: bool get_use_clipping_planes() const { return m_use_clipping_planes; } const std::array &get_clipping_planes() const { return m_clipping_planes; }; - void set_color_by(const std::string& value); + void set_use_color_clip_plane(bool use) { m_volumes.set_use_color_clip_plane(use); } + void set_color_clip_plane(const Vec3d& cp_normal, double offset) { m_volumes.set_color_clip_plane(cp_normal, offset); } + void set_color_clip_plane_colors(const std::array& colors) { m_volumes.set_color_clip_plane_colors(colors); } void refresh_camera_scene_box(); @@ -857,15 +886,15 @@ public: void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type, bool use_top_view = false, bool for_picking = false); static void render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, ModelObjectPtrs& model_objects, - const GLVolumeCollection& volumes, std::vector>& extruder_colors, + const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type, bool use_top_view = false, bool for_picking = false); // render thumbnail using an off-screen framebuffer static void render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, - PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector>& extruder_colors, + PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type, bool use_top_view = false, bool for_picking = false); // render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported static void render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, - PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector>& extruder_colors, + PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type, bool use_top_view = false, bool for_picking = false); //BBS use gcoder viewer render calibration thumbnails @@ -937,7 +966,6 @@ public: void do_move(const std::string& snapshot_type); void do_rotate(const std::string& snapshot_type); void do_scale(const std::string& snapshot_type); - void do_flatten(const Vec3d& normal, const std::string& snapshot_type); void do_center(); void do_mirror(const std::string& snapshot_type); @@ -1111,27 +1139,25 @@ private: void _picking_pass(); void _rectangular_selection_picking_pass(); - void _render_background() const; - void _render_bed(bool bottom, bool show_axes); - void _render_bed_for_picking(bool bottom); + void _render_background(); + void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes); //BBS: add part plate related logic - void _render_platelist(bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false) const; - void _render_plates_for_picking() const; + void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false); //BBS: add outline drawing logic void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true); //BBS: GUI refactor: add canvas size as parameters void _render_gcode(int canvas_width, int canvas_height); //BBS: render a plane for assemble void _render_plane() const; - void _render_selection() const; + void _render_selection(); void _render_sequential_clearance(); #if ENABLE_RENDER_SELECTION_CENTER - void _render_selection_center() const; + void _render_selection_center(); #endif // ENABLE_RENDER_SELECTION_CENTER void _check_and_update_toolbar_icon_scale(); void _render_overlays(); void _render_style_editor(); - void _render_volumes_for_picking() const; + void _render_volumes_for_picking(const Camera& camera) const; void _render_current_gizmo() const; void _render_gizmos_overlay(); void _render_main_toolbar(); @@ -1147,15 +1173,15 @@ private: void _render_assemble_control() const; void _render_assemble_info() const; #if ENABLE_SHOW_CAMERA_TARGET - void _render_camera_target() const; + void _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET void _render_sla_slices(); - void _render_selection_sidebar_hints() const; + void _render_selection_sidebar_hints(); //BBS: GUI refactor: adjust main toolbar position bool _render_orient_menu(float left, float right, float bottom, float top); bool _render_arrange_menu(float left, float right, float bottom, float top); // render thumbnail using the default framebuffer - void render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector>& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type); + void render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type); void _update_volumes_hover_state(); @@ -1201,8 +1227,6 @@ private: // BBS FIXME float get_overlay_window_width() { return 0; /*LayersEditing::get_overlay_window_width();*/ } - - static std::vector> _parse_colors(const std::vector& colors); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 422b654080..959c9aa9ac 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2020 - 2023 Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r/libslic3r.h" #include "GLModel.hpp" @@ -8,135 +12,513 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Polygon.hpp" +#include "libslic3r/BuildVolume.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include #include +#if ENABLE_SMOOTH_NORMALS +#include +#include +#include +#endif // ENABLE_SMOOTH_NORMALS + #include namespace Slic3r { namespace GUI { -size_t GLModel::InitializationData::vertices_count() const +#if ENABLE_SMOOTH_NORMALS +static void smooth_normals_corner(const TriangleMesh& mesh, std::vector& normals) { - size_t ret = 0; - for (const Entity& entity : entities) { - ret += entity.positions.size(); + using MapMatrixXfUnaligned = Eigen::Map>; + using MapMatrixXiUnaligned = Eigen::Map>; + + std::vector face_normals = its_face_normals(mesh.its); + + Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), + Eigen::Index(mesh.its.vertices.size()), 3).cast(); + Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(), + Eigen::Index(mesh.its.indices.size()), 3); + Eigen::MatrixXd in_normals = MapMatrixXfUnaligned(face_normals.front().data(), + Eigen::Index(face_normals.size()), 3).cast(); + Eigen::MatrixXd out_normals; + + igl::per_corner_normals(vertices, indices, in_normals, 1.0, out_normals); + + normals = std::vector(mesh.its.vertices.size()); + for (size_t i = 0; i < mesh.its.indices.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + normals[mesh.its.indices[i][j]] = out_normals.row(i * 3 + j).cast(); + } } - return ret; +} +#endif // ENABLE_SMOOTH_NORMALS + +void GLModel::Geometry::add_vertex(const Vec2f& position) +{ + assert(format.vertex_layout == EVertexLayout::P2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); } -size_t GLModel::InitializationData::indices_count() const +void GLModel::Geometry::add_vertex(const Vec2f& position, const Vec2f& tex_coord) { - size_t ret = 0; - for (const Entity& entity : entities) { - ret += entity.indices.size(); - } - return ret; + assert(format.vertex_layout == EVertexLayout::P2T2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(tex_coord.x()); + vertices.emplace_back(tex_coord.y()); } -void GLModel::init_from(const InitializationData& data) +void GLModel::Geometry::add_vertex(const Vec3f& position) { - if (!m_render_data.empty()) // call reset() if you want to reuse this model + assert(format.vertex_layout == EVertexLayout::P3); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec2f& tex_coord) +{ + assert(format.vertex_layout == EVertexLayout::P3T2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(tex_coord.x()); + vertices.emplace_back(tex_coord.y()); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal) +{ + assert(format.vertex_layout == EVertexLayout::P3N3); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(normal.x()); + vertices.emplace_back(normal.y()); + vertices.emplace_back(normal.z()); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord) +{ + assert(format.vertex_layout == EVertexLayout::P3N3T2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(normal.x()); + vertices.emplace_back(normal.y()); + vertices.emplace_back(normal.z()); + vertices.emplace_back(tex_coord.x()); + vertices.emplace_back(tex_coord.y()); +} + +void GLModel::Geometry::add_vertex(const Vec4f& position) +{ + assert(format.vertex_layout == EVertexLayout::P4); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(position.w()); +} + +void GLModel::Geometry::add_index(unsigned int id) +{ + indices.emplace_back(id); +} + +void GLModel::Geometry::add_line(unsigned int id1, unsigned int id2) +{ + indices.emplace_back(id1); + indices.emplace_back(id2); +} + +void GLModel::Geometry::add_triangle(unsigned int id1, unsigned int id2, unsigned int id3) +{ + indices.emplace_back(id1); + indices.emplace_back(id2); + indices.emplace_back(id3); +} + +Vec2f GLModel::Geometry::extract_position_2(size_t id) const +{ + const size_t p_stride = position_stride_floats(format); + if (p_stride != 2) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)]; + return { *(start + 0), *(start + 1) }; +} + +Vec3f GLModel::Geometry::extract_position_3(size_t id) const +{ + const size_t p_stride = position_stride_floats(format); + if (p_stride != 3) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)]; + return { *(start + 0), *(start + 1), *(start + 2) }; +} + +Vec3f GLModel::Geometry::extract_normal_3(size_t id) const +{ + const size_t n_stride = normal_stride_floats(format); + if (n_stride != 3) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + normal_offset_floats(format)]; + return { *(start + 0), *(start + 1), *(start + 2) }; +} + +Vec2f GLModel::Geometry::extract_tex_coord_2(size_t id) const +{ + const size_t t_stride = tex_coord_stride_floats(format); + if (t_stride != 2) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + tex_coord_offset_floats(format)]; + return { *(start + 0), *(start + 1) }; +} + +void GLModel::Geometry::set_vertex(size_t id, const Vec3f& position, const Vec3f& normal) +{ + assert(format.vertex_layout == EVertexLayout::P3N3); + assert(id < vertices_count()); + if (id < vertices_count()) { + float* start = &vertices[id * vertex_stride_floats(format)]; + *(start + 0) = position.x(); + *(start + 1) = position.y(); + *(start + 2) = position.z(); + *(start + 3) = normal.x(); + *(start + 4) = normal.y(); + *(start + 5) = normal.z(); + } +} + +void GLModel::Geometry::set_index(size_t id, unsigned int index) +{ + assert(id < indices_count()); + if (id < indices_count()) + indices[id] = index; +} + +unsigned int GLModel::Geometry::extract_index(size_t id) const +{ + if (indices_count() <= id) { + assert(false); + return -1; + } + + return indices[id]; +} + +void GLModel::Geometry::remove_vertex(size_t id) +{ + assert(id < vertices_count()); + if (id < vertices_count()) { + const size_t stride = vertex_stride_floats(format); + std::vector::const_iterator it = vertices.begin() + id * stride; + vertices.erase(it, it + stride); + } +} + +indexed_triangle_set GLModel::Geometry::get_as_indexed_triangle_set() const +{ + indexed_triangle_set its; + its.vertices.reserve(vertices_count()); + for (size_t i = 0; i < vertices_count(); ++i) { + its.vertices.emplace_back(extract_position_3(i)); + } + its.indices.reserve(indices_count() / 3); + for (size_t i = 0; i < indices_count() / 3; ++i) { + const size_t tri_id = i * 3; + its.indices.emplace_back(extract_index(tri_id), extract_index(tri_id + 1), extract_index(tri_id + 2)); + } + return its; +} + +size_t GLModel::Geometry::vertex_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: { return 2; } + case EVertexLayout::P2T2: { return 4; } + case EVertexLayout::P3: { return 3; } + case EVertexLayout::P3T2: { return 5; } + case EVertexLayout::P3N3: { return 6; } + case EVertexLayout::P3N3T2: { return 8; } + case EVertexLayout::P4: { return 4; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::position_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: { return 2; } + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: { return 3; } + case EVertexLayout::P4: { return 4; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::position_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: + case EVertexLayout::P4: { return 0; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::normal_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: { return 3; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::normal_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: { return 3; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::tex_coord_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3T2: { return 2; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::tex_coord_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: { return 2; } + case EVertexLayout::P3T2: { return 3; } + case EVertexLayout::P3N3T2: { return 6; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::index_stride_bytes(const Geometry& data) +{ + switch (data.index_type) + { + case EIndexType::UINT: { return sizeof(unsigned int); } + case EIndexType::USHORT: { return sizeof(unsigned short); } + case EIndexType::UBYTE: { return sizeof(unsigned char); } + default: { assert(false); return 0; } + }; +} + +bool GLModel::Geometry::has_position(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: + case EVertexLayout::P4: { return true; } + default: { assert(false); return false; } + }; +} + +bool GLModel::Geometry::has_normal(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P4: { return false; } + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: { return true; } + default: { assert(false); return false; } + }; +} + +bool GLModel::Geometry::has_tex_coord(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3T2: { return true; } + case EVertexLayout::P2: + case EVertexLayout::P3: + case EVertexLayout::P3N3: + case EVertexLayout::P4: { return false; } + default: { assert(false); return false; } + }; +} + +void GLModel::init_from(Geometry&& data) +{ + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); return; + } - for (const InitializationData::Entity& entity : data.entities) { - if (entity.positions.empty() || entity.indices.empty()) - continue; + if (data.vertices.empty() || data.indices.empty()) { + assert(false); + return; + } - assert(entity.normals.empty() || entity.normals.size() == entity.positions.size()); + m_render_data.geometry = std::move(data); - RenderData rdata; - rdata.type = entity.type; - rdata.color = entity.color; - - // vertices/normals data - std::vector vertices(6 * entity.positions.size()); - for (size_t i = 0; i < entity.positions.size(); ++i) { - const size_t offset = i * 6; - ::memcpy(static_cast(&vertices[offset]), static_cast(entity.positions[i].data()), 3 * sizeof(float)); - if (!entity.normals.empty()) - ::memcpy(static_cast(&vertices[3 + offset]), static_cast(entity.normals[i].data()), 3 * sizeof(float)); + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + const size_t position_stride = Geometry::position_stride_floats(data.format); + if (position_stride == 3) + m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast()); + else if (position_stride == 2) { + const Vec2f position = m_render_data.geometry.extract_position_2(i); + m_bounding_box.merge(Vec3f(position.x(), position.y(), 0.0f).cast()); } - - // indices data - std::vector indices = entity.indices; - - rdata.indices_count = static_cast(indices.size()); - - // update bounding box - for (size_t i = 0; i < entity.positions.size(); ++i) { - m_bounding_box.merge(entity.positions[i].cast()); - } - - send_to_gpu(rdata, vertices, indices); - m_render_data.emplace_back(rdata); } } -void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bbox) +void GLModel::init_from(const TriangleMesh& mesh) { - if (!m_render_data.empty()) // call reset() if you want to reuse this model - return; - - RenderData data; - data.type = PrimitiveType::Triangles; - - std::vector vertices = std::vector(18 * its.indices.size()); - std::vector indices = std::vector(3 * its.indices.size()); - - unsigned int vertices_count = 0; - for (uint32_t i = 0; i < its.indices.size(); ++i) { - stl_triangle_vertex_indices face = its.indices[i]; - stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; - stl_vertex n = face_normal_normalized(vertex); - for (size_t j = 0; j < 3; ++ j) { - size_t offset = i * 18 + j * 6; - ::memcpy(static_cast(&vertices[offset]), static_cast(vertex[j].data()), 3 * sizeof(float)); - ::memcpy(static_cast(&vertices[3 + offset]), static_cast(n.data()), 3 * sizeof(float)); - } - for (size_t j = 0; j < 3; ++j) - indices[i * 3 + j] = vertices_count + j; - vertices_count += 3; - } - - data.indices_count = static_cast(indices.size()); - m_bounding_box = bbox; - - send_to_gpu(data, vertices, indices); - m_render_data.emplace_back(data); + init_from(mesh.its); } void GLModel::init_from(const indexed_triangle_set& its) { - this->init_from(its, bounding_box(its)); + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + if (its.vertices.empty() || its.indices.empty()){ + assert(false); + return; + } + + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(3 * its.indices.size()); + data.reserve_indices(3 * its.indices.size()); + + // vertices + indices + unsigned int vertices_counter = 0; + for (uint32_t i = 0; i < its.indices.size(); ++i) { + const stl_triangle_vertex_indices face = its.indices[i]; + const stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; + const stl_vertex n = face_normal_normalized(vertex); + for (size_t j = 0; j < 3; ++j) { + data.add_vertex(vertex[j], n); + } + vertices_counter += 3; + data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(data.extract_position_3(i).cast()); + } } void GLModel::init_from(const Polygons& polygons, float z) { - auto append_polygon = [](const Polygon& polygon, float z, GUI::GLModel::InitializationData& data) { - if (!polygon.empty()) { - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::LineLoop; - // contour - entity.positions.reserve(polygon.size() + 1); - entity.indices.reserve(polygon.size() + 1); - unsigned int id = 0; - for (const Point& p : polygon) { - Vec3f position = unscale(p.x(), p.y(), 0.0).cast(); - position.z() = z; - entity.positions.emplace_back(position); - entity.indices.emplace_back(id++); - } - data.entities.emplace_back(entity); - } - }; - - InitializationData init_data; - for (const Polygon& polygon : polygons) { - append_polygon(polygon, z, init_data); + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + if (polygons.empty()) { + assert(false); + return; + } + + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Lines, Geometry::EVertexLayout::P3 }; + + size_t segments_count = 0; + for (const Polygon& polygon : polygons) { + segments_count += polygon.points.size(); + } + + data.reserve_vertices(2 * segments_count); + data.reserve_indices(2 * segments_count); + + // vertices + indices + unsigned int vertices_counter = 0; + for (const Polygon& poly : polygons) { + for (size_t i = 0; i < poly.points.size(); ++i) { + const Point& p0 = poly.points[i]; + const Point& p1 = (i == poly.points.size() - 1) ? poly.points.front() : poly.points[i + 1]; + data.add_vertex(Vec3f(unscale(p0.x()), unscale(p0.y()), z)); + data.add_vertex(Vec3f(unscale(p1.x()), unscale(p1.y()), z)); + vertices_counter += 2; + data.add_line(vertices_counter - 2, vertices_counter - 1); + } + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(data.extract_position_3(i).cast()); } - init_from(init_data); } bool GLModel::init_from_file(const std::string& filename) @@ -148,206 +530,342 @@ bool GLModel::init_from_file(const std::string& filename) return false; Model model; - try - { + try { model = Model::read_from_file(filename); } - catch (std::exception&) - { + catch (std::exception&) { return false; } - TriangleMesh mesh = model.mesh(); - init_from(mesh.its, mesh.bounding_box()); + init_from(model.mesh()); m_filename = filename; return true; } -void GLModel::set_color(int entity_id, const std::array& color) -{ - for (size_t i = 0; i < m_render_data.size(); ++i) { - if (entity_id == -1 || static_cast(i) == entity_id) - m_render_data[i].color = color; - } -} - void GLModel::reset() { - for (RenderData& data : m_render_data) { - // release gpu memory - if (data.ibo_id > 0) - glsafe(::glDeleteBuffers(1, &data.ibo_id)); - if (data.vbo_id > 0) - glsafe(::glDeleteBuffers(1, &data.vbo_id)); + // release gpu memory + if (m_render_data.ibo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_render_data.ibo_id)); + m_render_data.ibo_id = 0; + } + if (m_render_data.vbo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_render_data.vbo_id)); + m_render_data.vbo_id = 0; } - m_render_data.clear(); + m_render_data.vertices_count = 0; + m_render_data.indices_count = 0; + m_render_data.geometry.vertices = std::vector(); + m_render_data.geometry.indices = std::vector(); m_bounding_box = BoundingBoxf3(); m_filename = std::string(); } -void GLModel::render() const +static GLenum get_primitive_mode(const GLModel::Geometry::Format& format) { - GLShaderProgram* shader = wxGetApp().get_current_shader(); - - for (const RenderData& data : m_render_data) { - if (data.vbo_id == 0 || data.ibo_id == 0) - continue; - - GLenum mode; - switch (data.type) - { - default: - case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } - case PrimitiveType::Lines: { mode = GL_LINES; break; } - case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } - case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } - } - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)0)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - if (shader != nullptr) - shader->set_uniform("uniform_color", data.color); - else - glsafe(::glColor4fv(data.color.data())); - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id)); - glsafe(::glDrawElements(mode, static_cast(data.indices_count), GL_UNSIGNED_INT, (const void*)0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + switch (format.type) + { + case GLModel::Geometry::EPrimitiveType::Points: { return GL_POINTS; } + default: + case GLModel::Geometry::EPrimitiveType::Triangles: { return GL_TRIANGLES; } + case GLModel::Geometry::EPrimitiveType::TriangleStrip: { return GL_TRIANGLE_STRIP; } + case GLModel::Geometry::EPrimitiveType::TriangleFan: { return GL_TRIANGLE_FAN; } + case GLModel::Geometry::EPrimitiveType::Lines: { return GL_LINES; } + case GLModel::Geometry::EPrimitiveType::LineStrip: { return GL_LINE_STRIP; } + case GLModel::Geometry::EPrimitiveType::LineLoop: { return GL_LINE_LOOP; } } } -void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) const +static GLenum get_index_type(const GLModel::Geometry& data) { - if (instances_vbo == 0) + switch (data.index_type) + { + default: + case GLModel::Geometry::EIndexType::UINT: { return GL_UNSIGNED_INT; } + case GLModel::Geometry::EIndexType::USHORT: { return GL_UNSIGNED_SHORT; } + case GLModel::Geometry::EIndexType::UBYTE: { return GL_UNSIGNED_BYTE; } + } +} + +void GLModel::render() +{ + render(std::make_pair(0, indices_count())); +} + +void GLModel::render(const std::pair& range) +{ + if (m_render_disabled) + return; + + if (range.second == range.first) return; GLShaderProgram* shader = wxGetApp().get_current_shader(); - assert(shader == nullptr || boost::algorithm::iends_with(shader->get_name(), "_instanced")); + if (shader == nullptr) + return; - // vertex attributes - GLint position_id = (shader != nullptr) ? shader->get_attrib_location("v_position") : -1; - GLint normal_id = (shader != nullptr) ? shader->get_attrib_location("v_normal") : -1; - assert(position_id != -1 && normal_id != -1); - - // instance attributes - GLint offset_id = (shader != nullptr) ? shader->get_attrib_location("i_offset") : -1; - GLint scales_id = (shader != nullptr) ? shader->get_attrib_location("i_scales") : -1; - assert(offset_id != -1 && scales_id != -1); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instances_vbo)); - if (offset_id != -1) { - glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)0)); - glsafe(::glEnableVertexAttribArray(offset_id)); - glsafe(::glVertexAttribDivisor(offset_id, 1)); - } - if (scales_id != -1) { - glsafe(::glVertexAttribPointer(scales_id, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)(3 * sizeof(float)))); - glsafe(::glEnableVertexAttribArray(scales_id)); - glsafe(::glVertexAttribDivisor(scales_id, 1)); + // sends data to gpu if not done yet + if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) { + if (m_render_data.geometry.vertices_count() > 0 && m_render_data.geometry.indices_count() > 0 && !send_to_gpu()) + return; } - for (const RenderData& data : m_render_data) { - if (data.vbo_id == 0 || data.ibo_id == 0) - continue; + const Geometry& data = m_render_data.geometry; - GLenum mode; - switch (data.type) - { - default: - case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } - case PrimitiveType::Lines: { mode = GL_LINES; break; } - case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } - case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } - } + const GLenum mode = get_primitive_mode(data.format); + const GLenum index_type = get_index_type(data); - if (shader != nullptr) - shader->set_uniform("uniform_color", data.color); - else - glsafe(::glColor4fv(data.color.data())); + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + const bool tex_coord = Geometry::has_tex_coord(data.format); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + + int position_id = -1; + int normal_id = -1; + int tex_coord_id = -1; + + if (position) { + position_id = shader->get_attrib_location("v_position"); if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)0)); + glsafe(::glVertexAttribPointer(position_id, Geometry::position_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::position_offset_bytes(data.format))); glsafe(::glEnableVertexAttribArray(position_id)); } + } + if (normal) { + normal_id = shader->get_attrib_location("v_normal"); if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)(3 * sizeof(float)))); + glsafe(::glVertexAttribPointer(normal_id, Geometry::normal_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::normal_offset_bytes(data.format))); glsafe(::glEnableVertexAttribArray(normal_id)); } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id)); - glsafe(::glDrawElementsInstanced(mode, static_cast(data.indices_count), GL_UNSIGNED_INT, (const void*)0, instances_count)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); + } + if (tex_coord) { + tex_coord_id = shader->get_attrib_location("v_tex_coord"); + if (tex_coord_id != -1) { + glsafe(::glVertexAttribPointer(tex_coord_id, Geometry::tex_coord_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::tex_coord_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(tex_coord_id)); + } } - if (scales_id != -1) - glsafe(::glDisableVertexAttribArray(scales_id)); - if (offset_id != -1) - glsafe(::glDisableVertexAttribArray(offset_id)); + shader->set_uniform("uniform_color", data.color); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -} - -void GLModel::send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices) -{ - assert(data.vbo_id == 0); - assert(data.ibo_id == 0); - - // vertex data -> send to gpu - glsafe(::glGenBuffers(1, &data.vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - // indices data -> send to gpu - glsafe(::glGenBuffers(1, &data.ibo_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glDrawElements(mode, range.second - range.first, index_type, (const void*)(range.first * Geometry::index_stride_bytes(data)))); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (tex_coord_id != -1) + glsafe(::glDisableVertexAttribArray(tex_coord_id)); + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } -GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) +void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { - entity.positions.emplace_back(position); - entity.normals.emplace_back(normal); - }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { - entity.indices.emplace_back(v1); - entity.indices.emplace_back(v2); - entity.indices.emplace_back(v3); - }; + if (instances_vbo == 0 || instances_count == 0) + return; - resolution = std::max(4, resolution); + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader == nullptr || !boost::algorithm::iends_with(shader->get_name(), "_instanced")) + return; - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + // vertex attributes + const GLint position_id = shader->get_attrib_location("v_position"); + const GLint normal_id = shader->get_attrib_location("v_normal"); + if (position_id == -1 || normal_id == -1) + return; - const float angle_step = 2.0f * M_PI / static_cast(resolution); + // instance attributes + const GLint offset_id = shader->get_attrib_location("i_offset"); + const GLint scales_id = shader->get_attrib_location("i_scales"); + if (offset_id == -1 || scales_id == -1) + return; + + if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) { + if (!send_to_gpu()) + return; + } + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instances_vbo)); + const size_t instance_stride = 5 * sizeof(float); + glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, instance_stride, (const void*)0)); + glsafe(::glEnableVertexAttribArray(offset_id)); + glsafe(::glVertexAttribDivisor(offset_id, 1)); + + glsafe(::glVertexAttribPointer(scales_id, 2, GL_FLOAT, GL_FALSE, instance_stride, (const void*)(3 * sizeof(float)))); + glsafe(::glEnableVertexAttribArray(scales_id)); + glsafe(::glVertexAttribDivisor(scales_id, 1)); + + const Geometry& data = m_render_data.geometry; + + const GLenum mode = get_primitive_mode(data.format); + const GLenum index_type = get_index_type(data); + + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + + if (position) { + glsafe(::glVertexAttribPointer(position_id, Geometry::position_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(position_id)); + } + + if (normal) { + glsafe(::glVertexAttribPointer(normal_id, Geometry::normal_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + + shader->set_uniform("uniform_color", data.color); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glDrawElementsInstanced(mode, indices_count(), index_type, (const void*)0, instances_count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (normal) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position) + glsafe(::glDisableVertexAttribArray(position_id)); + + glsafe(::glDisableVertexAttribArray(scales_id)); + glsafe(::glDisableVertexAttribArray(offset_id)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +bool GLModel::send_to_gpu() +{ + if (m_render_data.vbo_id > 0 || m_render_data.ibo_id > 0) { + assert(false); + return false; + } + + Geometry& data = m_render_data.geometry; + if (data.vertices.empty() || data.indices.empty()) { + assert(false); + return false; + } + + // vertices + glsafe(::glGenBuffers(1, &m_render_data.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, data.vertices_size_bytes(), data.vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + m_render_data.vertices_count = vertices_count(); + data.vertices = std::vector(); + + // indices + glsafe(::glGenBuffers(1, &m_render_data.ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + const size_t indices_count = data.indices.size(); + if (m_render_data.vertices_count <= 256) { + // convert indices to unsigned char to save gpu memory + std::vector reduced_indices(indices_count); + for (size_t i = 0; i < indices_count; ++i) { + reduced_indices[i] = (unsigned char)data.indices[i]; + } + data.index_type = Geometry::EIndexType::UBYTE; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_count * sizeof(unsigned char), reduced_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + else if (m_render_data.vertices_count <= 65536) { + // convert indices to unsigned short to save gpu memory + std::vector reduced_indices(indices_count); + for (size_t i = 0; i < data.indices.size(); ++i) { + reduced_indices[i] = (unsigned short)data.indices[i]; + } + data.index_type = Geometry::EIndexType::USHORT; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_count * sizeof(unsigned short), reduced_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + else { + data.index_type = Geometry::EIndexType::UINT; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices_size_bytes(), data.indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + m_render_data.indices_count = indices_count; + data.indices = std::vector(); + + return true; +} + +template +inline bool all_vertices_inside(const GLModel::Geometry& geometry, Fn fn) +{ + const size_t position_stride_floats = geometry.position_stride_floats(geometry.format); + const size_t position_offset_floats = geometry.position_offset_floats(geometry.format); + assert(position_stride_floats == 3); + if (geometry.vertices.empty() || position_stride_floats != 3) + return false; + + for (auto it = geometry.vertices.begin(); it != geometry.vertices.end(); ) { + it += position_offset_floats; + if (!fn({ *it, *(it + 1), *(it + 2) })) + return false; + it += (geometry.vertex_stride_floats(geometry.format) - position_offset_floats - position_stride_floats); + } + return true; +} + +bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom) +{ + static constexpr const double epsilon = BuildVolume::BedEpsilon; + switch (volume.type()) { + case BuildVolume_Type::Rectangle: + { + BoundingBox3Base build_volume = volume.bounding_volume().inflated(epsilon); + if (volume.printable_height() == 0.0) + build_volume.max.z() = std::numeric_limits::max(); + if (ignore_bottom) + build_volume.min.z() = -std::numeric_limits::max(); + const BoundingBoxf3& model_box = model.get_bounding_box(); + return build_volume.contains(model_box.min) && build_volume.contains(model_box.max); + } + case BuildVolume_Type::Circle: + { + const Geometry::Circled& circle = volume.circle(); + const Vec2f c = unscaled(circle.center); + const float r = unscaled(circle.radius) + float(epsilon); + const float r2 = sqr(r); + return volume.printable_height() == 0.0 ? + all_vertices_inside(model.get_geometry(), [c, r2](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2; }) : + + all_vertices_inside(model.get_geometry(), [c, r2, z = volume.printable_height() + epsilon](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; }); + } + case BuildVolume_Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case BuildVolume_Type::Custom: + return volume.printable_height() == 0.0 ? + all_vertices_inside(model.get_geometry(), [&volume](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast()); }) : + all_vertices_inside(model.get_geometry(), [&volume, z = volume.printable_height() + epsilon](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast()) && p.z() <= z; }); + default: + return true; + } +} + +GLModel::Geometry stilized_arrow(unsigned int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) +{ + resolution = std::max(4, resolution); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(6 * resolution + 2); + data.reserve_indices(6 * resolution * 3); + + const float angle_step = 2.0f * float(PI) / float(resolution); std::vector cosines(resolution); std::vector sines(resolution); - for (int i = 0; i < resolution; ++i) { - const float angle = angle_step * static_cast(i); + for (unsigned int i = 0; i < resolution; ++i) { + const float angle = angle_step * float(i); cosines[i] = ::cos(angle); sines[i] = -::sin(angle); } @@ -355,86 +873,76 @@ GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, flo const float total_height = tip_height + stem_height; // tip vertices/normals - append_vertex(entity, { 0.0f, 0.0f, total_height }, Vec3f::UnitZ()); - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); + data.add_vertex(Vec3f(0.0f, 0.0f, total_height), (Vec3f)Vec3f::UnitZ()); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(tip_radius * sines[i], tip_radius * cosines[i], stem_height), Vec3f(sines[i], cosines[i], 0.0f)); } // tip triangles - for (int i = 0; i < resolution; ++i) { - const int v3 = (i < resolution - 1) ? i + 2 : 1; - append_indices(entity, 0, i + 1, v3); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v3 = (i < resolution - 1) ? i + 2 : 1; + data.add_triangle(0, i + 1, v3); } // tip cap outer perimeter vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(tip_radius * sines[i], tip_radius * cosines[i], stem_height), (Vec3f)(-Vec3f::UnitZ())); } // tip cap inner perimeter vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], stem_height), (Vec3f)(-Vec3f::UnitZ())); } // tip cap triangles - for (int i = 0; i < resolution; ++i) { - const int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1; - const int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1; - append_indices(entity, i + resolution + 1, v3, v2); - append_indices(entity, i + resolution + 1, i + 2 * resolution + 1, v3); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1; + const unsigned int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1; + data.add_triangle(i + resolution + 1, v3, v2); + data.add_triangle(i + resolution + 1, i + 2 * resolution + 1, v3); } // stem bottom vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], stem_height), Vec3f(sines[i], cosines[i], 0.0f)); } // stem top vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, { sines[i], cosines[i], 0.0f }); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], 0.0f), Vec3f(sines[i], cosines[i], 0.0f)); } // stem triangles - for (int i = 0; i < resolution; ++i) { - const int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1; - const int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1; - append_indices(entity, i + 3 * resolution + 1, v3, v2); - append_indices(entity, i + 3 * resolution + 1, i + 4 * resolution + 1, v3); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1; + const unsigned int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1; + data.add_triangle(i + 3 * resolution + 1, v3, v2); + data.add_triangle(i + 3 * resolution + 1, i + 4 * resolution + 1, v3); } // stem cap vertices - append_vertex(entity, Vec3f::Zero(), -Vec3f::UnitZ()); - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, -Vec3f::UnitZ()); + data.add_vertex((Vec3f)Vec3f::Zero(), (Vec3f)(-Vec3f::UnitZ())); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], 0.0f), (Vec3f)(-Vec3f::UnitZ())); } // stem cap triangles - for (int i = 0; i < resolution; ++i) { - const int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2; - append_indices(entity, 5 * resolution + 1, v3, i + 5 * resolution + 2); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2; + data.add_triangle(5 * resolution + 1, v3, i + 5 * resolution + 2); } - data.entities.emplace_back(entity); return data; } -GLModel::InitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness) +GLModel::Geometry circular_arrow(unsigned int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { - entity.positions.emplace_back(position); - entity.normals.emplace_back(normal); - }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { - entity.indices.emplace_back(v1); - entity.indices.emplace_back(v2); - entity.indices.emplace_back(v3); - }; + resolution = std::max(2, resolution); - resolution = std::max(2, resolution); - - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(8 * (resolution + 1) + 30); + data.reserve_indices((8 * resolution + 16) * 3); const float half_thickness = 0.5f * thickness; const float half_stem_width = 0.5f * stem_width; @@ -442,171 +950,161 @@ GLModel::InitializationData circular_arrow(int resolution, float radius, float t const float outer_radius = radius + half_stem_width; const float inner_radius = radius - half_stem_width; - const float step_angle = 0.5f * PI / static_cast(resolution); + const float step_angle = 0.5f * float(PI) / float(resolution); // tip // top face vertices - append_vertex(entity, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -tip_height, radius, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, outer_radius, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-tip_height, radius, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, inner_radius, half_thickness), (Vec3f)Vec3f::UnitZ()); // top face triangles - append_indices(entity, 0, 1, 2); - append_indices(entity, 0, 2, 4); - append_indices(entity, 4, 2, 3); + data.add_triangle(0, 1, 2); + data.add_triangle(0, 2, 4); + data.add_triangle(4, 2, 3); // bottom face vertices - append_vertex(entity, { 0.0f, outer_radius, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius + half_tip_width, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -tip_height, radius, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, inner_radius, -half_thickness }, -Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, outer_radius, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-tip_height, radius, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, inner_radius, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); // bottom face triangles - append_indices(entity, 5, 7, 6); - append_indices(entity, 5, 9, 7); - append_indices(entity, 9, 8, 7); + data.add_triangle(5, 7, 6); + data.add_triangle(5, 9, 7); + data.add_triangle(9, 8, 7); // side faces vertices - append_vertex(entity, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, outer_radius, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, outer_radius, half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, half_thickness), (Vec3f)Vec3f::UnitX()); Vec3f normal(-half_tip_width, tip_height, 0.0f); normal.normalize(); - append_vertex(entity, { 0.0f, radius + half_tip_width, -half_thickness }, normal); - append_vertex(entity, { -tip_height, radius, -half_thickness }, normal); - append_vertex(entity, { 0.0f, radius + half_tip_width, half_thickness }, normal); - append_vertex(entity, { -tip_height, radius, half_thickness }, normal); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, -half_thickness), normal); + data.add_vertex(Vec3f(-tip_height, radius, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, half_thickness), normal); + data.add_vertex(Vec3f(-tip_height, radius, half_thickness), normal); - normal = Vec3f(-half_tip_width, -tip_height, 0.0f); + normal = { -half_tip_width, -tip_height, 0.0f }; normal.normalize(); - append_vertex(entity, { -tip_height, radius, -half_thickness }, normal); - append_vertex(entity, { 0.0f, radius - half_tip_width, -half_thickness }, normal); - append_vertex(entity, { -tip_height, radius, half_thickness }, normal); - append_vertex(entity, { 0.0f, radius - half_tip_width, half_thickness }, normal); + data.add_vertex(Vec3f(-tip_height, radius, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, -half_thickness), normal); + data.add_vertex(Vec3f(-tip_height, radius, half_thickness), normal); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, half_thickness), normal); - append_vertex(entity, { 0.0f, radius - half_tip_width, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, inner_radius, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, inner_radius, half_thickness), (Vec3f)Vec3f::UnitX()); // side face triangles - for (int i = 0; i < 4; ++i) { - const int ii = i * 4; - append_indices(entity, 10 + ii, 11 + ii, 13 + ii); - append_indices(entity, 10 + ii, 13 + ii, 12 + ii); + for (unsigned int i = 0; i < 4; ++i) { + const unsigned int ii = i * 4; + data.add_triangle(10 + ii, 11 + ii, 13 + ii); + data.add_triangle(10 + ii, 13 + ii, 12 + ii); } // stem // top face vertices - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness), (Vec3f)Vec3f::UnitZ()); } - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness), (Vec3f)Vec3f::UnitZ()); } // top face triangles - for (int i = 0; i < resolution; ++i) { - append_indices(entity, 26 + i, 27 + i, 27 + resolution + i); - append_indices(entity, 27 + i, 28 + resolution + i, 27 + resolution + i); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(26 + i, 27 + i, 27 + resolution + i); + data.add_triangle(27 + i, 28 + resolution + i, 27 + resolution + i); } // bottom face vertices - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness), (Vec3f)(-Vec3f::UnitZ())); } - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness), (Vec3f)(-Vec3f::UnitZ())); } // bottom face triangles - for (int i = 0; i < resolution; ++i) { - append_indices(entity, 28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i); - append_indices(entity, 29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i); + data.add_triangle(29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i); } // side faces vertices and triangles - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { inner_radius * s, inner_radius * c, -half_thickness }, { -s, -c, 0.0f }); + data.add_vertex(Vec3f(inner_radius * s, inner_radius * c, -half_thickness), Vec3f(-s, -c, 0.0f)); } - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f }); + data.add_vertex(Vec3f(inner_radius * s, inner_radius * c, half_thickness), Vec3f(-s, -c, 0.0f)); } - int first_id = 26 + 4 * (resolution + 1); - for (int i = 0; i < resolution; ++i) { - const int ii = first_id + i; - append_indices(entity, ii, ii + 1, ii + resolution + 2); - append_indices(entity, ii, ii + resolution + 2, ii + resolution + 1); + unsigned int first_id = 26 + 4 * (resolution + 1); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int ii = first_id + i; + data.add_triangle(ii, ii + 1, ii + resolution + 2); + data.add_triangle(ii, ii + resolution + 2, ii + resolution + 1); } - append_vertex(entity, { inner_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { outer_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { inner_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { outer_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); + data.add_vertex(Vec3f(inner_radius, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(outer_radius, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(inner_radius, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(outer_radius, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); first_id = 26 + 6 * (resolution + 1); - append_indices(entity, first_id, first_id + 1, first_id + 3); - append_indices(entity, first_id, first_id + 3, first_id + 2); + data.add_triangle(first_id, first_id + 1, first_id + 3); + data.add_triangle(first_id, first_id + 3, first_id + 2); for (int i = resolution; i >= 0; --i) { - const float angle = static_cast(i) * step_angle; + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { outer_radius * s, outer_radius * c, -half_thickness }, { s, c, 0.0f }); + data.add_vertex(Vec3f(outer_radius * s, outer_radius * c, -half_thickness), Vec3f(s, c, 0.0f)); } for (int i = resolution; i >= 0; --i) { - const float angle = static_cast(i) * step_angle; + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { outer_radius * s, outer_radius * c, +half_thickness }, { s, c, 0.0f }); + data.add_vertex(Vec3f(outer_radius * s, outer_radius * c, +half_thickness), Vec3f(s, c, 0.0f)); } first_id = 30 + 6 * (resolution + 1); - for (int i = 0; i < resolution; ++i) { - const int ii = first_id + i; - append_indices(entity, ii, ii + 1, ii + resolution + 2); - append_indices(entity, ii, ii + resolution + 2, ii + resolution + 1); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int ii = first_id + i; + data.add_triangle(ii, ii + 1, ii + resolution + 2); + data.add_triangle(ii, ii + resolution + 2, ii + resolution + 1); } - data.entities.emplace_back(entity); return data; } -GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness) +GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { - entity.positions.emplace_back(position); - entity.normals.emplace_back(normal); - }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { - entity.indices.emplace_back(v1); - entity.indices.emplace_back(v2); - entity.indices.emplace_back(v3); - }; - - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(42); + data.reserve_indices(72); const float half_thickness = 0.5f * thickness; const float half_stem_width = 0.5f * stem_width; @@ -614,133 +1112,307 @@ GLModel::InitializationData straight_arrow(float tip_width, float tip_height, fl const float total_height = tip_height + stem_height; // top face vertices - append_vertex(entity, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0, total_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_stem_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_tip_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, total_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-half_tip_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-half_stem_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, half_thickness), (Vec3f)Vec3f::UnitZ()); // top face triangles - append_indices(entity, 0, 1, 6); - append_indices(entity, 6, 1, 5); - append_indices(entity, 4, 5, 3); - append_indices(entity, 5, 1, 3); - append_indices(entity, 1, 2, 3); + data.add_triangle(0, 1, 6); + data.add_triangle(6, 1, 5); + data.add_triangle(4, 5, 3); + data.add_triangle(5, 1, 3); + data.add_triangle(1, 2, 3); // bottom face vertices - append_vertex(entity, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0, total_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, total_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); // bottom face triangles - append_indices(entity, 7, 13, 8); - append_indices(entity, 13, 12, 8); - append_indices(entity, 12, 11, 10); - append_indices(entity, 8, 12, 10); - append_indices(entity, 9, 8, 10); + data.add_triangle(7, 13, 8); + data.add_triangle(13, 12, 8); + data.add_triangle(12, 11, 10); + data.add_triangle(8, 12, 10); + data.add_triangle(9, 8, 10); // side faces vertices - append_vertex(entity, { half_stem_width, 0.0, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, stem_height, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, stem_height, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); + data.add_vertex(Vec3f(half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_stem_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_tip_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); Vec3f normal(tip_height, half_tip_width, 0.0f); normal.normalize(); - append_vertex(entity, { half_tip_width, stem_height, -half_thickness }, normal); - append_vertex(entity, { 0.0, total_height, -half_thickness }, normal); - append_vertex(entity, { half_tip_width, stem_height, half_thickness }, normal); - append_vertex(entity, { 0.0, total_height, half_thickness }, normal); + data.add_vertex(Vec3f(half_tip_width, stem_height, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, total_height, -half_thickness), normal); + data.add_vertex(Vec3f(half_tip_width, stem_height, half_thickness), normal); + data.add_vertex(Vec3f(0.0f, total_height, half_thickness), normal); - normal = Vec3f(-tip_height, half_tip_width, 0.0f); + normal = { -tip_height, half_tip_width, 0.0f }; normal.normalize(); - append_vertex(entity, { 0.0, total_height, -half_thickness }, normal); - append_vertex(entity, { -half_tip_width, stem_height, -half_thickness }, normal); - append_vertex(entity, { 0.0, total_height, half_thickness }, normal); - append_vertex(entity, { -half_tip_width, stem_height, half_thickness }, normal); + data.add_vertex(Vec3f(0.0f, total_height, -half_thickness), normal); + data.add_vertex(Vec3f(-half_tip_width, stem_height, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, total_height, half_thickness), normal); + data.add_vertex(Vec3f(-half_tip_width, stem_height, half_thickness), normal); - append_vertex(entity, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); + data.add_vertex(Vec3f(-half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_tip_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); - append_vertex(entity, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitX()); - append_vertex(entity, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitX()); - append_vertex(entity, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitX()); - append_vertex(entity, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitX()); + data.add_vertex(Vec3f(-half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitX())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitX())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitX())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitX())); - append_vertex(entity, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_stem_width, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); // side face triangles - for (int i = 0; i < 7; ++i) { - const int ii = i * 4; - append_indices(entity, 14 + ii, 15 + ii, 17 + ii); - append_indices(entity, 14 + ii, 17 + ii, 16 + ii); + for (unsigned int i = 0; i < 7; ++i) { + const unsigned int ii = i * 4; + data.add_triangle(14 + ii, 15 + ii, 17 + ii); + data.add_triangle(14 + ii, 17 + ii, 16 + ii); } - data.entities.emplace_back(entity); return data; } -GLModel::InitializationData diamond(int resolution) +GLModel::Geometry diamond(unsigned int resolution) { - resolution = std::max(4, resolution); + resolution = std::max(4, resolution); - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(resolution + 2); + data.reserve_indices((2 * (resolution + 1)) * 3); const float step = 2.0f * float(PI) / float(resolution); - // positions - for (int i = 0; i < resolution; ++i) { - float ii = float(i) * step; - entity.positions.emplace_back(0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f); - } - entity.positions.emplace_back(0.0f, 0.0f, 0.5f); - entity.positions.emplace_back(0.0f, 0.0f, -0.5f); - - // normals - for (const Vec3f& v : entity.positions) { - entity.normals.emplace_back(v.normalized()); + // vertices + for (unsigned int i = 0; i < resolution; ++i) { + const float ii = float(i) * step; + const Vec3f p = { 0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f }; + data.add_vertex(p, (Vec3f)p.normalized()); } + Vec3f p = { 0.0f, 0.0f, 0.5f }; + data.add_vertex(p, (Vec3f)p.normalized()); + p = { 0.0f, 0.0f, -0.5f }; + data.add_vertex(p, (Vec3f)p.normalized()); // triangles // top - for (int i = 0; i < resolution; ++i) { - entity.indices.push_back(i + 0); - entity.indices.push_back(i + 1); - entity.indices.push_back(resolution); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(i + 0, i + 1, resolution); } - entity.indices.push_back(resolution - 1); - entity.indices.push_back(0); - entity.indices.push_back(resolution); + data.add_triangle(resolution - 1, 0, resolution); // bottom - for (int i = 0; i < resolution; ++i) { - entity.indices.push_back(i + 0); - entity.indices.push_back(resolution + 1); - entity.indices.push_back(i + 1); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(i + 0, resolution + 1, i + 1); + } + data.add_triangle(resolution - 1, resolution + 1, 0); + + return data; +} + +GLModel::Geometry smooth_sphere(unsigned int resolution, float radius) +{ + resolution = std::max(4, resolution); + + const unsigned int sectorCount = resolution; + const unsigned int stackCount = resolution; + + const float sectorStep = float(2.0 * M_PI / sectorCount); + const float stackStep = float(M_PI / stackCount); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices((stackCount - 1) * sectorCount + 2); + data.reserve_indices((2 * (stackCount - 1) * sectorCount) * 3); + + // vertices + for (unsigned int i = 0; i <= stackCount; ++i) { + // from pi/2 to -pi/2 + const double stackAngle = 0.5 * M_PI - stackStep * i; + const double xy = double(radius) * ::cos(stackAngle); + const double z = double(radius) * ::sin(stackAngle); + if (i == 0 || i == stackCount) { + const Vec3f v(float(xy), 0.0f, float(z)); + data.add_vertex(v, (Vec3f)v.normalized()); + } + else { + for (unsigned int j = 0; j < sectorCount; ++j) { + // from 0 to 2pi + const double sectorAngle = sectorStep * j; + const Vec3f v(float(xy * std::cos(sectorAngle)), float(xy * std::sin(sectorAngle)), float(z)); + data.add_vertex(v, (Vec3f)v.normalized()); + } + } + } + + // triangles + for (unsigned int i = 0; i < stackCount; ++i) { + // Beginning of current stack. + unsigned int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + const unsigned int k1_first = k1; + // Beginning of next stack. + unsigned int k2 = (i == 0) ? 1 : (k1 + sectorCount); + const unsigned int k2_first = k2; + for (unsigned int j = 0; j < sectorCount; ++j) { + // 2 triangles per sector excluding first and last stacks + unsigned int k1_next = k1; + unsigned int k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + data.add_triangle(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + data.add_triangle(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + + return data; +} + +GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height) +{ + resolution = std::max(4, resolution); + + const unsigned int sectorCount = resolution; + const float sectorStep = 2.0f * float(M_PI) / float(sectorCount); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(sectorCount * 4 + 2); + data.reserve_indices(sectorCount * 4 * 3); + + auto generate_vertices_on_circle = [sectorCount, sectorStep](float radius) { + std::vector ret; + ret.reserve(sectorCount); + for (unsigned int i = 0; i < sectorCount; ++i) { + // from 0 to 2pi + const float sectorAngle = sectorStep * i; + ret.emplace_back(radius * std::cos(sectorAngle), radius * std::sin(sectorAngle), 0.0f); + } + return ret; + }; + + const std::vector base_vertices = generate_vertices_on_circle(radius); + const Vec3f h = height * Vec3f::UnitZ(); + + // stem vertices + for (unsigned int i = 0; i < sectorCount; ++i) { + const Vec3f& v = base_vertices[i]; + const Vec3f n = v.normalized(); + data.add_vertex(v, n); + data.add_vertex(v + h, n); + } + + // stem triangles + for (unsigned int i = 0; i < sectorCount; ++i) { + unsigned int v1 = i * 2; + unsigned int v2 = (i < sectorCount - 1) ? v1 + 2 : 0; + unsigned int v3 = v2 + 1; + unsigned int v4 = v1 + 1; + data.add_triangle(v1, v2, v3); + data.add_triangle(v1, v3, v4); + } + + // bottom cap vertices + Vec3f cap_center = Vec3f::Zero(); + unsigned int cap_center_id = data.vertices_count(); + Vec3f normal = -Vec3f::UnitZ(); + + data.add_vertex(cap_center, normal); + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_vertex(base_vertices[i], normal); + } + + // bottom cap triangles + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_triangle(cap_center_id, (i < sectorCount - 1) ? cap_center_id + i + 2 : cap_center_id + 1, cap_center_id + i + 1); + } + + // top cap vertices + cap_center += h; + cap_center_id = data.vertices_count(); + normal = -normal; + + data.add_vertex(cap_center, normal); + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_vertex(base_vertices[i] + h, normal); + } + + // top cap triangles + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_triangle(cap_center_id, cap_center_id + i + 1, (i < sectorCount - 1) ? cap_center_id + i + 2 : cap_center_id + 1); + } + + return data; +} + +GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness) +{ + const unsigned int torus_sector_count = std::max(4, primary_resolution); + const float torus_sector_step = 2.0f * float(M_PI) / float(torus_sector_count); + const unsigned int section_sector_count = std::max(4, secondary_resolution); + const float section_sector_step = 2.0f * float(M_PI) / float(section_sector_count); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(torus_sector_count * section_sector_count); + data.reserve_indices(torus_sector_count * section_sector_count * 2 * 3); + + // vertices + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const float section_angle = torus_sector_step * i; + const float csa = std::cos(section_angle); + const float ssa = std::sin(section_angle); + const Vec3f section_center(radius * csa, radius * ssa, 0.0f); + for (unsigned int j = 0; j < section_sector_count; ++j) { + const float circle_angle = section_sector_step * j; + const float thickness_xy = thickness * std::cos(circle_angle); + const float thickness_z = thickness * std::sin(circle_angle); + const Vec3f v(thickness_xy * csa, thickness_xy * ssa, thickness_z); + data.add_vertex(section_center + v, (Vec3f)v.normalized()); + } + } + + // triangles + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const unsigned int ii = i * section_sector_count; + const unsigned int ii_next = ((i + 1) % torus_sector_count) * section_sector_count; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const unsigned int j_next = (j + 1) % section_sector_count; + const unsigned int i0 = ii + j; + const unsigned int i1 = ii_next + j; + const unsigned int i2 = ii_next + j_next; + const unsigned int i3 = ii + j_next; + data.add_triangle(i0, i1, i2); + data.add_triangle(i0, i2, i3); + } } - entity.indices.push_back(resolution - 1); - entity.indices.push_back(resolution + 1); - entity.indices.push_back(0); - data.entities.emplace_back(entity); return data; } diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index d47c56fd93..e90c263ece 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -1,8 +1,13 @@ +///|/ Copyright (c) Prusa Research 2020 - 2023 Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLModel_hpp_ #define slic3r_GLModel_hpp_ #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Color.hpp" #include #include @@ -13,53 +18,139 @@ namespace Slic3r { class TriangleMesh; class Polygon; using Polygons = std::vector; +class BuildVolume; namespace GUI { class GLModel { public: - enum class PrimitiveType : unsigned char + struct Geometry { - Triangles, - Lines, - LineStrip, - LineLoop + enum class EPrimitiveType : unsigned char + { + Points, + Triangles, + TriangleStrip, + TriangleFan, + Lines, + LineStrip, + LineLoop + }; + + enum class EVertexLayout : unsigned char + { + P2, // position 2 floats + P2T2, // position 2 floats + texture coords 2 floats + P3, // position 3 floats + P3T2, // position 3 floats + texture coords 2 floats + P3N3, // position 3 floats + normal 3 floats + P3N3T2, // position 3 floats + normal 3 floats + texture coords 2 floats + P4, // position 4 floats + }; + + enum class EIndexType : unsigned char + { + UINT, // unsigned int + USHORT, // unsigned short + UBYTE // unsigned byte + }; + + struct Format + { + EPrimitiveType type{ EPrimitiveType::Triangles }; + EVertexLayout vertex_layout{ EVertexLayout::P3N3 }; + }; + + Format format; + std::vector vertices; + std::vector indices; + EIndexType index_type{ EIndexType::UINT }; + ColorRGBA color{ ColorRGBA::BLACK() }; + + void reserve_vertices(size_t vertices_count) { vertices.reserve(vertices_count * vertex_stride_floats(format)); } + void reserve_indices(size_t indices_count) { indices.reserve(indices_count); } + + void add_vertex(const Vec2f& position); // EVertexLayout::P2 + void add_vertex(const Vec2f& position, const Vec2f& tex_coord); // EVertexLayout::P2T2 + void add_vertex(const Vec3f& position); // EVertexLayout::P3 + void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2 + void add_vertex(const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 + void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord); // EVertexLayout::P3N3T2 + void add_vertex(const Vec4f& position); // EVertexLayout::P4 + + void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 + + void set_index(size_t id, unsigned int index); + + void add_index(unsigned int id); + void add_line(unsigned int id1, unsigned int id2); + void add_triangle(unsigned int id1, unsigned int id2, unsigned int id3); + + Vec2f extract_position_2(size_t id) const; + Vec3f extract_position_3(size_t id) const; + Vec3f extract_normal_3(size_t id) const; + Vec2f extract_tex_coord_2(size_t id) const; + + unsigned int extract_index(size_t id) const; + + void remove_vertex(size_t id); + + bool is_empty() const { return vertices_count() == 0 || indices_count() == 0; } + + size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); } + size_t indices_count() const { return indices.size(); } + + size_t vertices_size_floats() const { return vertices.size(); } + size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + size_t indices_size_bytes() const { return indices.size() * index_stride_bytes(*this); } + + indexed_triangle_set get_as_indexed_triangle_set() const; + + static size_t vertex_stride_floats(const Format& format); + static size_t vertex_stride_bytes(const Format& format) { return vertex_stride_floats(format) * sizeof(float); } + + static size_t position_stride_floats(const Format& format); + static size_t position_stride_bytes(const Format& format) { return position_stride_floats(format) * sizeof(float); } + static size_t position_offset_floats(const Format& format); + static size_t position_offset_bytes(const Format& format) { return position_offset_floats(format) * sizeof(float); } + + static size_t normal_stride_floats(const Format& format); + static size_t normal_stride_bytes(const Format& format) { return normal_stride_floats(format) * sizeof(float); } + static size_t normal_offset_floats(const Format& format); + static size_t normal_offset_bytes(const Format& format) { return normal_offset_floats(format) * sizeof(float); } + + static size_t tex_coord_stride_floats(const Format& format); + static size_t tex_coord_stride_bytes(const Format& format) { return tex_coord_stride_floats(format) * sizeof(float); } + static size_t tex_coord_offset_floats(const Format& format); + static size_t tex_coord_offset_bytes(const Format& format) { return tex_coord_offset_floats(format) * sizeof(float); } + + static size_t index_stride_bytes(const Geometry& data); + + static bool has_position(const Format& format); + static bool has_normal(const Format& format); + static bool has_tex_coord(const Format& format); }; struct RenderData { - PrimitiveType type; + Geometry geometry; unsigned int vbo_id{ 0 }; unsigned int ibo_id{ 0 }; + size_t vertices_count{ 0 }; size_t indices_count{ 0 }; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; }; - - struct InitializationData - { - struct Entity - { - PrimitiveType type; - std::vector positions; - std::vector normals; - std::vector indices; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; - }; - - std::vector entities; - - size_t vertices_count() const; - size_t vertices_size_floats() const { return vertices_count() * 6; } - size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } - - size_t indices_count() const; - size_t indices_size_bytes() const { return indices_count() * sizeof(unsigned int); } - }; - private: - std::vector m_render_data; + RenderData m_render_data; + // By default the vertex and index buffers data are sent to gpu at the first call to render() method. + // If you need to initialize a model from outside the main thread, so that a call to render() may happen + // before the initialization is complete, use the methods: + // disable_render() + // ... do your initialization ... + // enable_render() + // to keep the data on cpu side until needed. + bool m_render_disabled{ false }; BoundingBoxf3 m_bounding_box; std::string m_filename; @@ -67,50 +158,98 @@ namespace GUI { GLModel() = default; virtual ~GLModel() { reset(); } - void init_from(const InitializationData& data); - void init_from(const indexed_triangle_set& its, const BoundingBoxf3& bbox); + size_t vertices_count() const { return m_render_data.vertices_count > 0 ? + m_render_data.vertices_count : m_render_data.geometry.vertices_count(); } + size_t indices_count() const { return m_render_data.indices_count > 0 ? + m_render_data.indices_count : m_render_data.geometry.indices_count(); } + + size_t vertices_size_floats() const { return vertices_count() * Geometry::vertex_stride_floats(m_render_data.geometry.format); } + size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + + size_t indices_size_bytes() const { return indices_count() * Geometry::index_stride_bytes(m_render_data.geometry); } + + const Geometry& get_geometry() const { return m_render_data.geometry; } + + void init_from(Geometry&& data); + void init_from(const TriangleMesh& mesh); void init_from(const indexed_triangle_set& its); void init_from(const Polygons& polygons, float z); bool init_from_file(const std::string& filename); - // if entity_id == -1 set the color of all entities - void set_color(int entity_id, const std::array& color); + void set_color(const ColorRGBA& color) { m_render_data.geometry.color = color; } + const ColorRGBA& get_color() const { return m_render_data.geometry.color; } void reset(); - void render() const; - void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const; + void render(); + void render(const std::pair& range); + void render_instanced(unsigned int instances_vbo, unsigned int instances_count); - bool is_initialized() const { return !m_render_data.empty(); } + bool is_initialized() const { return vertices_count() > 0 && indices_count() > 0; } + bool is_empty() const { return m_render_data.geometry.is_empty(); } const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::string& get_filename() const { return m_filename; } + bool is_render_disabled() const { return m_render_disabled; } + void enable_render() { m_render_disabled = false; } + void disable_render() { m_render_disabled = true; } + + size_t cpu_memory_used() const { + size_t ret = 0; + if (!m_render_data.geometry.vertices.empty()) + ret += vertices_size_bytes(); + if (!m_render_data.geometry.indices.empty()) + ret += indices_size_bytes(); + return ret; + } + size_t gpu_memory_used() const { + size_t ret = 0; + if (m_render_data.geometry.vertices.empty()) + ret += vertices_size_bytes(); + if (m_render_data.geometry.indices.empty()) + ret += indices_size_bytes(); + return ret; + } + private: - void send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices); + bool send_to_gpu(); }; + bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom = true); // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution // the origin of the arrow is in the center of the stem cap // the arrow has its axis of symmetry along the Z axis and is pointing upward // used to render bed axes and sequential marker - GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); + GLModel::Geometry stilized_arrow(unsigned int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); // create an arrow whose stem is a quarter of circle, with the given dimensions and resolution // the origin of the arrow is in the center of the circle // the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise // used to render sidebar hints for rotations - GLModel::InitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); + GLModel::Geometry circular_arrow(unsigned int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); // create an arrow with the given dimensions // the origin of the arrow is in the center of the stem cap // the arrow is contained in XY plane and has its main axis along the Y axis // used to render sidebar hints for position and scale - GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); + GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); // create a diamond with the given resolution // the origin of the diamond is in its center // the diamond is contained into a box with size [1, 1, 1] - GLModel::InitializationData diamond(int resolution); + GLModel::Geometry diamond(unsigned int resolution); + + // create a sphere with smooth normals + // the origin of the sphere is in its center + GLModel::Geometry smooth_sphere(unsigned int resolution, float radius); + // create a cylinder with smooth normals + // the axis of the cylinder is the Z axis + // the origin of the cylinder is the center of its bottom cap face + GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height); + // create a torus with smooth normals + // the axis of the torus is the Z axis + // the origin of the torus is in its center + GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index f6dfbe10f7..68f9d096eb 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -1,5 +1,10 @@ +///|/ Copyright (c) Prusa Research 2019 - 2022 Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLSelectionRectangle.hpp" #include "Camera.hpp" +#include "CameraUtils.hpp" #include "3DScene.hpp" #include "GLCanvas3D.hpp" #include "GUI_App.hpp" @@ -29,35 +34,19 @@ namespace GUI { m_end_corner = mouse_position; } - std::vector GLSelectionRectangle::stop_dragging(const GLCanvas3D& canvas, const std::vector& points) + std::vector GLSelectionRectangle::contains(const std::vector& points) const { std::vector out; - if (!is_dragging()) - return out; - - m_state = Off; - - const Camera& camera = wxGetApp().plater()->get_camera(); - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - // Convert our std::vector to Eigen dynamic matrix. - Eigen::Matrix pts(points.size(), 3); - for (size_t i=0; i(i, 0) = points[i]; - - // Get the projections. - Eigen::Matrix projections; - igl::project(pts, modelview, projection, viewport, projections); - // bounding box created from the rectangle corners - will take care of order of the corners - BoundingBox rectangle(Points{ Point(m_start_corner.cast()), Point(m_end_corner.cast()) }); + const BoundingBox rectangle(Points{ Point(m_start_corner.cast()), Point(m_end_corner.cast()) }); // Iterate over all points and determine whether they're in the rectangle. - for (int i = 0; iget_camera(); + Points points_2d = CameraUtils::project(camera, points); + unsigned int size = static_cast(points.size()); + for (unsigned int i = 0; i< size; ++i) + if (rectangle.contains(points_2d[i])) out.push_back(i); return out; @@ -69,59 +58,70 @@ namespace GUI { m_state = Off; } - void GLSelectionRectangle::render(const GLCanvas3D& canvas) const + void GLSelectionRectangle::render(const GLCanvas3D& canvas) { if (!is_dragging()) return; - const Camera& camera = wxGetApp().plater()->get_camera(); - float inv_zoom = (float)camera.get_inv_zoom(); - - Size cnv_size = canvas.get_canvas_size(); - float cnv_half_width = 0.5f * (float)cnv_size.get_width(); - float cnv_half_height = 0.5f * (float)cnv_size.get_height(); - if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) + const Size cnv_size = canvas.get_canvas_size(); + const float cnv_width = (float)cnv_size.get_width(); + const float cnv_height = (float)cnv_size.get_height(); + if (cnv_width == 0.0f || cnv_height == 0.0f) return; - Vec2d start(m_start_corner(0) - cnv_half_width, cnv_half_height - m_start_corner(1)); - Vec2d end(m_end_corner(0) - cnv_half_width, cnv_half_height - m_end_corner(1)); - - float left = (float)std::min(start(0), end(0)) * inv_zoom; - float top = (float)std::max(start(1), end(1)) * inv_zoom; - float right = (float)std::max(start(0), end(0)) * inv_zoom; - float bottom = (float)std::min(start(1), end(1)) * inv_zoom; - + const float cnv_inv_width = 1.0f / cnv_width; + const float cnv_inv_height = 1.0f / cnv_height; + const float left = 2.0f * (get_left() * cnv_inv_width - 0.5f); + const float right = 2.0f * (get_right() * cnv_inv_width - 0.5f); + const float top = -2.0f * (get_top() * cnv_inv_height - 0.5f); + const float bottom = -2.0f * (get_bottom() * cnv_inv_height - 0.5f); + glsafe(::glLineWidth(1.5f)); - float color[3]; - color[0] = 0.00f; - color[1] = 1.00f; - color[2] = 0.38f; - glsafe(::glColor3fv(color)); glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the rectangle is renderered inside the frustrum - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); - // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); - glsafe(::glPushAttrib(GL_ENABLE_BIT)); glsafe(::glLineStipple(4, 0xAAAA)); glsafe(::glEnable(GL_LINE_STIPPLE)); - ::glBegin(GL_LINE_LOOP); - ::glVertex2f((GLfloat)left, (GLfloat)bottom); - ::glVertex2f((GLfloat)right, (GLfloat)bottom); - ::glVertex2f((GLfloat)right, (GLfloat)top); - ::glVertex2f((GLfloat)left, (GLfloat)top); - glsafe(::glEnd()); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + if (!m_rectangle.is_initialized() || !m_old_start_corner.isApprox(m_start_corner) || !m_old_end_corner.isApprox(m_end_corner)) { + m_old_start_corner = m_start_corner; + m_old_end_corner = m_end_corner; + m_rectangle.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(4); + + // vertices + init_data.add_vertex(Vec2f(left, bottom)); + init_data.add_vertex(Vec2f(right, bottom)); + init_data.add_vertex(Vec2f(right, top)); + init_data.add_vertex(Vec2f(left, top)); + + // indices + init_data.add_index(0); + init_data.add_index(1); + init_data.add_index(2); + init_data.add_index(3); + + m_rectangle.init_from(std::move(init_data)); + } + + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + + m_rectangle.set_color({0.0f, 1.0f, 0.38f, 1.0f}); + m_rectangle.render(); + shader->stop_using(); + } glsafe(::glPopAttrib()); - - glsafe(::glPopMatrix()); } } // namespace GUI diff --git a/src/slic3r/GUI/GLSelectionRectangle.hpp b/src/slic3r/GUI/GLSelectionRectangle.hpp index d9869771ef..8d87e18e46 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.hpp +++ b/src/slic3r/GUI/GLSelectionRectangle.hpp @@ -1,7 +1,12 @@ +///|/ Copyright (c) Prusa Research 2019 - 2022 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLSelectionRectangle_hpp_ #define slic3r_GLSelectionRectangle_hpp_ #include "libslic3r/Point.hpp" +#include "GLModel.hpp" namespace Slic3r { namespace GUI { @@ -24,28 +29,31 @@ public: void dragging(const Vec2d& mouse_position); // Given a vector of points in world coordinates, the function returns indices of those - // that are in the rectangle. It then disables the rectangle. - std::vector stop_dragging(const GLCanvas3D& canvas, const std::vector& points); + // that are in the rectangle. + std::vector contains(const std::vector& points) const; // Disables the rectangle. void stop_dragging(); - void render(const GLCanvas3D& canvas) const; + void render(const GLCanvas3D& canvas); bool is_dragging() const { return m_state != Off; } EState get_state() const { return m_state; } - float get_width() const { return std::abs(m_start_corner(0) - m_end_corner(0)); } - float get_height() const { return std::abs(m_start_corner(1) - m_end_corner(1)); } - float get_left() const { return std::min(m_start_corner(0), m_end_corner(0)); } - float get_right() const { return std::max(m_start_corner(0), m_end_corner(0)); } - float get_top() const { return std::max(m_start_corner(1), m_end_corner(1)); } - float get_bottom() const { return std::min(m_start_corner(1), m_end_corner(1)); } + float get_width() const { return std::abs(m_start_corner.x() - m_end_corner.x()); } + float get_height() const { return std::abs(m_start_corner.y() - m_end_corner.y()); } + float get_left() const { return std::min(m_start_corner.x(), m_end_corner.x()); } + float get_right() const { return std::max(m_start_corner.x(), m_end_corner.x()); } + float get_top() const { return std::max(m_start_corner.y(), m_end_corner.y()); } + float get_bottom() const { return std::min(m_start_corner.y(), m_end_corner.y()); } private: - EState m_state = Off; - Vec2d m_start_corner; - Vec2d m_end_corner; + EState m_state{ Off }; + Vec2d m_start_corner{ Vec2d::Zero() }; + Vec2d m_end_corner{ Vec2d::Zero() }; + GLModel m_rectangle; + Vec2d m_old_start_corner{ Vec2d::Zero() }; + Vec2d m_old_end_corner{ Vec2d::Zero() }; }; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 9c1e936525..5146f03fc6 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -4,6 +4,7 @@ #include "3DScene.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/format.hpp" +#include "libslic3r/Color.hpp" #include #include @@ -121,8 +122,7 @@ bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSourc for (size_t i = 0; i < static_cast(EShaderType::Count); ++i) { const std::string& source = sources[i]; - if (!source.empty()) - { + if (!source.empty()) { EShaderType type = static_cast(i); auto [result, id] = create_shader(type); if (result) @@ -206,154 +206,147 @@ void GLShaderProgram::stop_using() const glsafe(::glUseProgram(0)); } -bool GLShaderProgram::set_uniform(const char* name, int value) const +void GLShaderProgram::set_uniform(int id, int value) const { - int id = get_uniform_location(name); - if (id >= 0) { - glsafe(::glUniform1i(id, static_cast(value))); - return true; - } - return false; + if (id >= 0) + glsafe(::glUniform1i(id, value)); } -bool GLShaderProgram::set_uniform(const char* name, bool value) const +void GLShaderProgram::set_uniform(int id, bool value) const { - return set_uniform(name, value ? 1 : 0); + set_uniform(id, value ? 1 : 0); } -bool GLShaderProgram::set_uniform(const char* name, float value) const +void GLShaderProgram::set_uniform(int id, float value) const { - int id = get_uniform_location(name); - if (id >= 0) { - glsafe(::glUniform1f(id, static_cast(value))); - return true; - } - return false; + if (id >= 0) + glsafe(::glUniform1f(id, value)); } -bool GLShaderProgram::set_uniform(const char* name, double value) const +void GLShaderProgram::set_uniform(int id, double value) const { - return set_uniform(name, static_cast(value)); + set_uniform(id, static_cast(value)); } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform2iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform4iv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform2fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniform4fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t size) const +void GLShaderProgram::set_uniform(int id, const std::array& value) const { - if (size == 1) - return set_uniform(name, value[0]); - else if (size < 5) { - int id = get_uniform_location(name); - if (id >= 0) { - if (size == 2) - glsafe(::glUniform2fv(id, 1, static_cast(value))); - else if (size == 3) - glsafe(::glUniform3fv(id, 1, static_cast(value))); - else - glsafe(::glUniform4fv(id, 1, static_cast(value))); - - return true; - } - } - return false; + const std::array f_value = { float(value[0]), float(value[1]), float(value[2]), float(value[3]) }; + set_uniform(id, f_value); } -bool GLShaderProgram::set_uniform(const char* name, const Transform3f& value) const +void GLShaderProgram::set_uniform(int id, const float* value, size_t size) const { - int id = get_uniform_location(name); if (id >= 0) { + if (size == 1) + set_uniform(id, value[0]); + else if (size == 2) + glsafe(::glUniform2fv(id, 1, static_cast(value))); + else if (size == 3) + glsafe(::glUniform3fv(id, 1, static_cast(value))); + else if (size == 4) + glsafe(::glUniform4fv(id, 1, static_cast(value))); + } +} + +void GLShaderProgram::set_uniform(int id, const Transform3f& value) const +{ + if (id >= 0) glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.matrix().data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) const +void GLShaderProgram::set_uniform(int id, const Transform3d& value) const { - return set_uniform(name, value.cast()); + set_uniform(id, value.cast()); } -bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const +void GLShaderProgram::set_uniform(int id, const Matrix3f& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + if (id >= 0) glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const +void GLShaderProgram::set_uniform(int id, const Matrix3d& value) const { - int id = get_uniform_location(name); - if (id >= 0) { + set_uniform(id, (Matrix3f)value.cast()); +} + +void GLShaderProgram::set_uniform(int id, const Matrix4f& value) const +{ + if (id >= 0) + glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const Matrix4d& value) const +{ + set_uniform(id, (Matrix4f)value.cast()); +} + +void GLShaderProgram::set_uniform(int id, const Vec2f& value) const +{ + if (id >= 0) + glsafe(::glUniform2fv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const Vec2d& value) const +{ + set_uniform(id, static_cast(value.cast())); +} + +void GLShaderProgram::set_uniform(int id, const Vec3f& value) const +{ + if (id >= 0) glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); - return true; - } - return false; } -bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const +void GLShaderProgram::set_uniform(int id, const Vec3d& value) const { - return set_uniform(name, static_cast(value.cast())); + set_uniform(id, static_cast(value.cast())); +} + +void GLShaderProgram::set_uniform(int id, const ColorRGB& value) const +{ + set_uniform(id, value.data(), 3); +} + +void GLShaderProgram::set_uniform(int id, const ColorRGBA& value) const +{ + set_uniform(id, value.data(), 4); } int GLShaderProgram::get_attrib_location(const char* name) const diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index d7b92000df..935daaaeaa 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -9,6 +9,9 @@ namespace Slic3r { +class ColorRGB; +class ColorRGBA; + class GLShaderProgram { public: @@ -44,22 +47,55 @@ public: void start_using() const; void stop_using() const; - bool set_uniform(const char* name, int value) const; - bool set_uniform(const char* name, bool value) const; - bool set_uniform(const char* name, float value) const; - bool set_uniform(const char* name, double value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const std::array& value) const; - bool set_uniform(const char* name, const float* value, size_t size) const; - bool set_uniform(const char* name, const Transform3f& value) const; - bool set_uniform(const char* name, const Transform3d& value) const; - bool set_uniform(const char* name, const Matrix3f& value) const; - bool set_uniform(const char* name, const Vec3f& value) const; - bool set_uniform(const char* name, const Vec3d& value) const; + void set_uniform(const char* name, int value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, bool value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, float value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, double value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const std::array& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const float* value, size_t size) const { set_uniform(get_uniform_location(name), value, size); } + void set_uniform(const char* name, const Transform3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Transform3d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Matrix3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Matrix3d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Matrix4f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Matrix4d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec2f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec2d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec3f& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const Vec3d& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const ColorRGB& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const ColorRGBA& value) const { set_uniform(get_uniform_location(name), value); } + + void set_uniform(int id, int value) const; + void set_uniform(int id, bool value) const; + void set_uniform(int id, float value) const; + void set_uniform(int id, double value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const float* value, size_t size) const; + void set_uniform(int id, const Transform3f& value) const; + void set_uniform(int id, const Transform3d& value) const; + void set_uniform(int id, const Matrix3f& value) const; + void set_uniform(int id, const Matrix3d& value) const; + void set_uniform(int id, const Matrix4f& value) const; + void set_uniform(int id, const Matrix4d& value) const; + void set_uniform(int id, const Vec2f& value) const; + void set_uniform(int id, const Vec2d& value) const; + void set_uniform(int id, const Vec3f& value) const; + void set_uniform(int id, const Vec3d& value) const; + void set_uniform(int id, const ColorRGB& value) const; + void set_uniform(int id, const ColorRGBA& value) const; // returns -1 if not found int get_attrib_location(const char* name) const; diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 107fcb5627..d525ce272e 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -33,61 +33,48 @@ std::pair GLShadersManager::init() bool valid = true; + const std::string prefix = GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 1) ? "140/" : "110/"; + // imgui shader + valid &= append_shader("imgui", { prefix + "imgui.vs", prefix + "imgui.fs" }); + // basic shader, used to render all what was previously rendered using the immediate mode + valid &= append_shader("flat", { prefix + "flat.vs", prefix + "flat.fs" }); + // basic shader with plane clipping, used to render volumes in picking pass + valid &= append_shader("flat_clip", { prefix + "flat_clip.vs", prefix + "flat_clip.fs" }); + // basic shader for textures, used to render textures + valid &= append_shader("flat_texture", { prefix + "flat_texture.vs", prefix + "flat_texture.fs" }); + // used to render 3D scene background + valid &= append_shader("background", { prefix + "background.vs", prefix + "background.fs" }); // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview - valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); + valid &= append_shader("gouraud_light", { prefix + "gouraud_light.vs", prefix + "gouraud_light.fs" }); //used to render thumbnail - valid &= append_shader("thumbnail", { "thumbnail.vs", "thumbnail.fs" }); - // used to render first layer for calibration - valid &= append_shader("cali", { "cali.vs", "cali.fs"}); + valid &= append_shader("thumbnail", { prefix + "thumbnail.vs", prefix + "thumbnail.fs"}); // used to render printbed - valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); + valid &= append_shader("printbed", { prefix + "printbed.vs", prefix + "printbed.fs" }); // used to render options in gcode preview - if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) - valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" }); - // used to render extrusion and travel paths as lines in gcode preview - valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); + if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { + valid &= append_shader("gouraud_light_instanced", { prefix + "gouraud_light_instanced.vs", prefix + "gouraud_light_instanced.fs" }); + } // used to render objects in 3d editor - //if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 0)) { - if (0) { - valid &= append_shader("gouraud", { "gouraud_130.vs", "gouraud_130.fs" } -#if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } -#endif // ENABLE_ENVIRONMENT_MAP - ); - } - else { - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } + valid &= append_shader("gouraud", { prefix + "gouraud.vs", prefix + "gouraud.fs" } #if ENABLE_ENVIRONMENT_MAP , { "ENABLE_ENVIRONMENT_MAP"sv } #endif // ENABLE_ENVIRONMENT_MAP ); - } // used to render variable layers heights in 3d editor - valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); + valid &= append_shader("variable_layer_height", { prefix + "variable_layer_height.vs", prefix + "variable_layer_height.fs" }); // used to render highlight contour around selected triangles inside the multi-material gizmo - valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" }); + valid &= append_shader("mm_contour", { prefix + "mm_contour.vs", prefix + "mm_contour.fs" }); // Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader. // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. // Because of this, objects had darker colors inside the multi-material gizmo. // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. // Since macOS 12 (Monterey), this issue with the opposite direction on Apple's Arm CPU seems to be fixed, and computed // triangle normals inside fragment shader have the right direction. - if (platform_flavor() == PlatformFlavor::OSXOnArm && wxPlatformInfo::Get().GetOSMajorVersion() < 12) { - //if (GUI::wxGetApp().plater() && GUI::wxGetApp().plater()->is_wireframe_enabled()) - // valid &= append_shader("mm_gouraud", {"mm_gouraud_wireframe.vs", "mm_gouraud_wireframe.fs"}, {"FLIP_TRIANGLE_NORMALS"sv}); - //else - valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv}); - } - else { - //if (GUI::wxGetApp().plater() && GUI::wxGetApp().plater()->is_wireframe_enabled()) - // valid &= append_shader("mm_gouraud", {"mm_gouraud_wireframe.vs", "mm_gouraud_wireframe.fs"}); - //else - valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}); - } - - //BBS: add shader for outline - valid &= append_shader("outline", { "outline.vs", "outline.fs" }); + if (platform_flavor() == PlatformFlavor::OSXOnArm && wxPlatformInfo::Get().GetOSMajorVersion() < 12) + valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv }); + else + valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" }); return { valid, error }; } diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 0b15866943..dba3523491 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Lukáš Hejl @hejllukas, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ //BBS:add i18n #include "I18N.hpp" //BBS: add fstream for debug output @@ -8,6 +12,8 @@ #include "3DScene.hpp" #include "OpenGLManager.hpp" +#include "GUI_App.hpp" +#include "GLModel.hpp" #include @@ -125,11 +131,7 @@ void GLTexture::Compressor::compress() GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; GLTexture::GLTexture() - : m_id(0) - , m_width(0) - , m_height(0) - , m_source("") - , m_compressor(*this) + : m_compressor(*this) { } @@ -418,13 +420,13 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vectorstart_using(); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + model.render(); + shader->stop_using(); + } glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); @@ -677,9 +699,29 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, glsafe(::glDisable(GL_BLEND)); } +static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h) +{ + auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; }; + auto upper_power_of_two = [](int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }; + + int new_w = std::max(w, h); + if (!is_power_of_two(new_w)) + new_w = upper_power_of_two(new_w); + + while (new_w > max_size_px) { + new_w /= 2; + } + + const int new_h = new_w; + const bool ret = (new_w != w || new_h != h); + w = new_w; + h = new_h; + return ret; +} + bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) { - bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc; + const bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported(); // Load a PNG with an alpha channel. wxImage image; @@ -693,6 +735,11 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo bool requires_rescale = false; + if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) { + if (to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), OpenGLManager::get_gl_info().get_max_tex_size(), m_width, m_height)) + requires_rescale = true; + } + if (compression_enabled && compression_type == MultiThreaded) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 int width_rem = m_width % 4; @@ -819,7 +866,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo m_source = filename; - if (compression_enabled && compression_type == MultiThreaded) + if (compression_type == MultiThreaded) // start asynchronous compression m_compressor.start_compressing(); @@ -828,7 +875,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { - bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; + const bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported(); NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); if (image == nullptr) { @@ -836,11 +883,17 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo return false; } - float scale = (float)max_size_px / std::max(image->width, image->height); + const float scale = (float)max_size_px / std::max(image->width, image->height); m_width = (int)(scale * image->width); m_height = (int)(scale * image->height); + if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) + to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), max_size_px, m_width, m_height); + + float scale_w = (float)m_width / image->width; + float scale_h = (float)m_height / image->height; + if (compression_enabled) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 int width_rem = m_width % 4; @@ -853,7 +906,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo m_height += (4 - height_rem); } - int n_pixels = m_width * m_height; + const int n_pixels = m_width * m_height; if (n_pixels <= 0) { reset(); @@ -870,7 +923,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo // creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps std::vector data(n_pixels * 4, 0); - nsvgRasterize(rast, image, 0, 0, scale, data.data(), m_width, m_height, m_width * 4); + nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), m_width, m_height, m_width * 4); // sends data to gpu glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); @@ -892,7 +945,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) { + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards int lod_w = m_width; int lod_h = m_height; @@ -902,11 +955,12 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo lod_w = std::max(lod_w / 2, 1); lod_h = std::max(lod_h / 2, 1); - scale /= 2.0f; + scale_w /= 2.0f; + scale_h /= 2.0f; data.resize(lod_w * lod_h * 4); - nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); + nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), lod_w, lod_h, lod_w * 4); if (compression_enabled) { // initializes the texture on GPU glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); @@ -921,9 +975,8 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } - } else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { - glGenerateMipmap(GL_TEXTURE_2D); - } else { + } + else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); } diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index d898a10bdf..9af4f8a872 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -64,8 +64,8 @@ namespace GUI { struct UV { - float u; - float v; + float u{ 0.0f }; + float v{ 0.0f }; }; struct Quad_UVs @@ -79,9 +79,9 @@ namespace GUI { static Quad_UVs FullTextureUVs; protected: - unsigned int m_id; - int m_width; - int m_height; + unsigned int m_id{ 0 }; + int m_width{ 0 }; + int m_height{ 0 }; std::string m_source; Compressor m_compressor; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 6a78edd907..8e99bb1bc2 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2022 Enrico Turri @enricoturri1966, David Kocík @kocikdav, Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r/Point.hpp" #include "libslic3r/libslic3r.h" @@ -281,21 +285,13 @@ bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) return res; } -bool GLToolbar::init_arrow(const BackgroundTexture::Metadata& arrow_texture) +bool GLToolbar::init_arrow(const std::string& filename) { - if (m_arrow_texture.texture.get_id() != 0) + if (m_arrow_texture.get_id() != 0) return true; - std::string path = resources_dir() + "/images/"; - bool res = false; - - if (!arrow_texture.filename.empty()) { - res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000); - } - if (res) - m_arrow_texture.metadata = arrow_texture; - - return res; + const std::string path = resources_dir() + "/images/"; + return (!filename.empty()) ? m_arrow_texture.load_from_svg_file(path + filename, false, false, false, 1000) : false; } GLToolbar::Layout::EType GLToolbar::get_layout_type() const @@ -551,18 +547,16 @@ void GLToolbar::render(const GLCanvas3D& parent,GLToolbarItem::EType type) { default: case Layout::Horizontal: { render_horizontal(parent,type); break; } - case Layout::Vertical: { render_vertical(parent); break; } + case Layout::Vertical: { render_vertical(parent); break; } } } - - bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { if (!m_enabled) return false; - Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); + const Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); bool processed = false; // mouse anywhere @@ -610,7 +604,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) return false; } - int item_id = contains_mouse(mouse_pos, parent); + const int item_id = contains_mouse(mouse_pos, parent); if (item_id != -1) { // mouse inside toolbar if (evt.LeftDown() || evt.LeftDClick()) { @@ -757,16 +751,12 @@ int GLToolbar::get_visible_items_cnt() const void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas3D& parent, bool check_hover) { - if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id)) - { - if ((0 <= item_id) && (item_id < (int)m_items.size())) - { + if (m_pressed_toggable_id == -1 || m_pressed_toggable_id == item_id) { + if (0 <= item_id && item_id < (int)m_items.size()) { GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && !item->is_disabled() && (!check_hover || item->is_hovered())) - { - if (((type == GLToolbarItem::Right) && item->is_right_toggable()) || - ((type == GLToolbarItem::Left) && item->is_left_toggable())) - { + if (item != nullptr && !item->is_separator() && !item->is_disabled() && (!check_hover || item->is_hovered())) { + if ((type == GLToolbarItem::Right && item->is_right_toggable()) || + (type == GLToolbarItem::Left && item->is_left_toggable())) { GLToolbarItem::EState state = item->get_state(); if (state == GLToolbarItem::Hover) item->set_state(GLToolbarItem::HoverPressed); @@ -784,12 +774,11 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas switch (type) { default: - case GLToolbarItem::Left: { item->do_left_action(); break; } + case GLToolbarItem::Left: { item->do_left_action(); break; } case GLToolbarItem::Right: { item->do_right_action(); break; } } } - else - { + else { if (m_type == Radio) select_item(item->get_name()); else @@ -804,8 +793,7 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas case GLToolbarItem::Right: { item->do_right_action(); break; } } - if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) - { + if (m_type == Normal && item->get_state() != GLToolbarItem::Disabled) { // the item may get disabled during the action, if not, set it back to normal state item->set_state(GLToolbarItem::Normal); parent.render(); @@ -825,55 +813,51 @@ void GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) { default: case Layout::Horizontal: { update_hover_state_horizontal(mouse_pos, parent); break; } - case Layout::Vertical: { update_hover_state_vertical(mouse_pos, parent); break; } + case Layout::Vertical: { update_hover_state_vertical(mouse_pos, parent); break; } } } void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) { - // NB: mouse_pos is already scaled appropriately + const Size cnv_size = parent.get_canvas_size(); + const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float factor = m_layout.scale * inv_zoom; + const float icons_size = m_layout.icons_size * m_layout.scale; + const float separator_size = m_layout.separator_size * m_layout.scale; + const float gap_size = m_layout.gap_size * m_layout.scale; + const float border = m_layout.border * m_layout.scale; - Size cnv_size = parent.get_canvas_size(); - Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + const float separator_stride = separator_size + gap_size; + const float icon_stride = icons_size + gap_size; - float scaled_icons_size = m_layout.icons_size * factor; - float scaled_separator_size = m_layout.separator_size * factor; - float scaled_gap_size = m_layout.gap_size * factor; - float scaled_border = m_layout.border * factor; + float left = m_layout.left + border; + float top = m_layout.top - border; - float separator_stride = scaled_separator_size + scaled_gap_size; - float icon_stride = scaled_icons_size + scaled_gap_size; - - float left = m_layout.left + scaled_border; - float top = m_layout.top - scaled_border; - - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { if (!item->is_visible()) continue; if (item->is_separator()) left += separator_stride; - else - { - float right = left + scaled_icons_size; - float bottom = top - scaled_icons_size; + else { + float right = left + icons_size; + const float bottom = top - icons_size; //BBS: GUI refactor: GLToolbar if (item->is_action_with_text()) - right += scaled_icons_size * item->get_extra_size_ratio(); - GLToolbarItem::EState state = item->get_state(); - bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); + right += icons_size * item->get_extra_size_ratio(); + + const GLToolbarItem::EState state = item->get_state(); + bool inside = (left <= (float)scaled_mouse_pos.x()) && + ((float)scaled_mouse_pos.x() <= right) && + (bottom <= (float)scaled_mouse_pos.y()) && + ((float)scaled_mouse_pos.y() <= top); switch (state) { case GLToolbarItem::Normal: { - if (inside) - { + if (inside) { item->set_state(GLToolbarItem::Hover); parent.set_as_dirty(); } @@ -882,8 +866,7 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D } case GLToolbarItem::Hover: { - if (!inside) - { + if (!inside) { item->set_state(GLToolbarItem::Normal); parent.set_as_dirty(); } @@ -892,8 +875,7 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D } case GLToolbarItem::Pressed: { - if (inside) - { + if (inside) { item->set_state(GLToolbarItem::HoverPressed); parent.set_as_dirty(); } @@ -902,8 +884,7 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D } case GLToolbarItem::HoverPressed: { - if (!inside) - { + if (!inside) { item->set_state(GLToolbarItem::Pressed); parent.set_as_dirty(); } @@ -912,8 +893,7 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D } case GLToolbarItem::Disabled: { - if (inside) - { + if (inside) { item->set_state(GLToolbarItem::HoverDisabled); parent.set_as_dirty(); } @@ -922,8 +902,7 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D } case GLToolbarItem::HoverDisabled: { - if (!inside) - { + if (!inside) { item->set_state(GLToolbarItem::Disabled); parent.set_as_dirty(); } @@ -939,59 +918,55 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D left += icon_stride; //BBS: GUI refactor: GLToolbar if (item->is_action_with_text()) - left += scaled_icons_size * item->get_extra_size_ratio(); + left += icons_size * item->get_extra_size_ratio(); } } } void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) { - // NB: mouse_pos is already scaled appropriately + const Size cnv_size = parent.get_canvas_size(); + const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float factor = m_layout.scale * inv_zoom; + const float icons_size = m_layout.icons_size * m_layout.scale; + const float separator_size = m_layout.separator_size * m_layout.scale; + const float gap_size = m_layout.gap_size * m_layout.scale; + const float border = m_layout.border * m_layout.scale; - Size cnv_size = parent.get_canvas_size(); - Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + const float separator_stride = separator_size + gap_size; + const float icon_stride = icons_size + gap_size; - float scaled_icons_size = m_layout.icons_size * factor; - float scaled_separator_size = m_layout.separator_size * factor; - float scaled_gap_size = m_layout.gap_size * factor; - float scaled_border = m_layout.border * factor; - float separator_stride = scaled_separator_size + scaled_gap_size; - float icon_stride = scaled_icons_size + scaled_gap_size; + float left = m_layout.left + border; + float top = m_layout.top - border; - float left = m_layout.left + scaled_border; - float top = m_layout.top - scaled_border; - - for (GLToolbarItem* item : m_items) - { + for (GLToolbarItem* item : m_items) { if (!item->is_visible()) continue; if (item->is_separator()) top -= separator_stride; - else - { - float right = left + scaled_icons_size; - float bottom = top - scaled_icons_size; + else { + float right = left + icons_size; + const float bottom = top - icons_size; if (item->is_action_with_text_image()) - right += m_layout.text_size * factor; + right += m_layout.text_size * m_layout.scale; //BBS: GUI refactor: GLToolbar if (item->is_action_with_text()) - right += scaled_icons_size * item->get_extra_size_ratio(); + right += icons_size * item->get_extra_size_ratio(); GLToolbarItem::EState state = item->get_state(); - bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); + const bool inside = (left <= (float)scaled_mouse_pos.x()) && + ((float)scaled_mouse_pos.x() <= right) && + (bottom <= (float)scaled_mouse_pos.y()) && + ((float)scaled_mouse_pos.y() <= top); switch (state) { case GLToolbarItem::Normal: { - if (inside) - { + if (inside) { item->set_state(GLToolbarItem::Hover); parent.set_as_dirty(); } @@ -1000,8 +975,7 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& } case GLToolbarItem::Hover: { - if (!inside) - { + if (!inside) { item->set_state(GLToolbarItem::Normal); parent.set_as_dirty(); } @@ -1010,8 +984,7 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& } case GLToolbarItem::Pressed: { - if (inside) - { + if (inside) { item->set_state(GLToolbarItem::HoverPressed); parent.set_as_dirty(); } @@ -1020,8 +993,7 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& } case GLToolbarItem::HoverPressed: { - if (!inside) - { + if (!inside) { item->set_state(GLToolbarItem::Pressed); parent.set_as_dirty(); } @@ -1030,8 +1002,7 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& } case GLToolbarItem::Disabled: { - if (inside) - { + if (inside) { item->set_state(GLToolbarItem::HoverDisabled); parent.set_as_dirty(); } @@ -1040,8 +1011,7 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& } case GLToolbarItem::HoverDisabled: { - if (!inside) - { + if (!inside) { item->set_state(GLToolbarItem::Disabled); parent.set_as_dirty(); } @@ -1083,77 +1053,78 @@ int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) { default: case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos, parent); } - case Layout::Vertical: { return contains_mouse_vertical(mouse_pos, parent); } + case Layout::Vertical: { return contains_mouse_vertical(mouse_pos, parent); } } } int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const { - // NB: mouse_pos is already scaled appropriately + const Size cnv_size = parent.get_canvas_size(); + const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float factor = m_layout.scale * inv_zoom; + const float icons_size = m_layout.icons_size * m_layout.scale; + const float separator_size = m_layout.separator_size * m_layout.scale; + const float gap_size = m_layout.gap_size * m_layout.scale; + const float border = m_layout.border * m_layout.scale; - Size cnv_size = parent.get_canvas_size(); - Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + float left = m_layout.left + border; + const float top = m_layout.top - border; - float scaled_icons_size = m_layout.icons_size * factor; - float scaled_separator_size = m_layout.separator_size * factor; - float scaled_gap_size = m_layout.gap_size * factor; - float scaled_border = m_layout.border * factor; - - float left = m_layout.left + scaled_border; - float top = m_layout.top - scaled_border; - - - for (size_t id=0; idis_visible()) continue; - if (item->is_separator()) - { - float right = left + scaled_separator_size; - float bottom = top - scaled_icons_size; + if (item->is_separator()) { + float right = left + separator_size; + const float bottom = top - icons_size; // mouse inside the separator - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return id; left = right; - right += scaled_gap_size; + right += gap_size; - if (id < m_items.size() - 1) - { + if (id < m_items.size() - 1) { // mouse inside the gap - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return -2; } left = right; } - else - { - float right = left + scaled_icons_size; - float bottom = top - scaled_icons_size; + else { + float right = left + icons_size; + const float bottom = top - icons_size; //BBS: GUI refactor: GLToolbar if (item->is_action_with_text()) - right += scaled_icons_size * item->get_extra_size_ratio(); + right += icons_size * item->get_extra_size_ratio(); // mouse inside the icon - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return id; left = right; - right += scaled_gap_size; + right += gap_size; - if (id < m_items.size() - 1) - { + if (id < m_items.size() - 1) { // mouse inside the gap - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return -2; } @@ -1166,73 +1137,75 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const { - // NB: mouse_pos is already scaled appropriately + const Size cnv_size = parent.get_canvas_size(); + const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float factor = m_layout.scale * inv_zoom; + const float icons_size = m_layout.icons_size * m_layout.scale; + const float separator_size = m_layout.separator_size * m_layout.scale; + const float gap_size = m_layout.gap_size * m_layout.scale; + const float border = m_layout.border * m_layout.scale; - Size cnv_size = parent.get_canvas_size(); - Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + const float left = m_layout.left + border; + float top = m_layout.top - border; - float scaled_icons_size = m_layout.icons_size * factor; - float scaled_separator_size = m_layout.separator_size * factor; - float scaled_gap_size = m_layout.gap_size * factor; - float scaled_border = m_layout.border * factor; - - float left = m_layout.left + scaled_border; - float top = m_layout.top - scaled_border; - - for (size_t id=0; idis_visible()) continue; - if (item->is_separator()) - { - float right = left + scaled_icons_size; - float bottom = top - scaled_separator_size; + if (item->is_separator()) { + const float right = left + icons_size; + float bottom = top - separator_size; // mouse inside the separator - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return id; top = bottom; - bottom -= scaled_gap_size; + bottom -= gap_size; - if (id < m_items.size() - 1) - { + if (id < m_items.size() - 1) { // mouse inside the gap - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return -2; } top = bottom; } - else - { - float right = left + scaled_icons_size; - float bottom = top - scaled_icons_size; + else { + float right = left + icons_size; + float bottom = top - icons_size; if (item->is_action_with_text_image()) - right += m_layout.text_size * factor; + right += m_layout.text_size * m_layout.scale; //BBS: GUI refactor: GLToolbar if (item->is_action_with_text()) - right += scaled_icons_size * item->get_extra_size_ratio(); + right += icons_size * item->get_extra_size_ratio(); // mouse inside the icon - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return id; top = bottom; - bottom -= scaled_gap_size; + bottom -= gap_size; - if (id < m_items.size() - 1) - { + if (id < m_items.size() - 1) { // mouse inside the gap - if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + if (left <= (float)scaled_mouse_pos.x() && + (float)scaled_mouse_pos.x() <= right && + bottom <= (float)scaled_mouse_pos.y() && + (float)scaled_mouse_pos.y() <= top) return -2; } @@ -1243,33 +1216,32 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& return -1; } -void GLToolbar::render_background(float left, float top, float right, float bottom, float border) const +void GLToolbar::render_background(float left, float top, float right, float bottom, float border_w, float border_h) const { - unsigned int tex_id = m_background_texture.texture.get_id(); - float tex_width = (float)m_background_texture.texture.get_width(); - float tex_height = (float)m_background_texture.texture.get_height(); - if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) - { - float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; - float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + const unsigned int tex_id = m_background_texture.texture.get_id(); + const float tex_width = (float)m_background_texture.texture.get_width(); + const float tex_height = (float)m_background_texture.texture.get_height(); + if (tex_id != 0 && tex_width > 0.0f && tex_height > 0.0f) { + const float inv_tex_width = 1.0f / tex_width; + const float inv_tex_height = 1.0f / tex_height; - float internal_left = left + border; - float internal_right = right - border; - float internal_top = top - border; - float internal_bottom = bottom + border; + const float internal_left = left + border_w; + const float internal_right = right - border_w; + const float internal_top = top - border_h; + const float internal_bottom = bottom + border_w; - float left_uv = 0.0f; - float right_uv = 1.0f; - float top_uv = 1.0f; - float bottom_uv = 0.0f; + const float left_uv = 0.0f; + const float right_uv = 1.0f; + const float top_uv = 1.0f; + const float bottom_uv = 0.0f; - float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; - float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; - float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; - float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + const float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; + const float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; + const float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; + const float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; // top-left corner - if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Top)) + if (m_layout.horizontal_orientation == Layout::HO_Left || m_layout.vertical_orientation == Layout::VO_Top) GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); else GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { left_uv, internal_top_uv }, { internal_left_uv, internal_top_uv }, { internal_left_uv, top_uv }, { left_uv, top_uv } }); @@ -1281,7 +1253,7 @@ void GLToolbar::render_background(float left, float top, float right, float bott GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, top_uv }, { internal_left_uv, top_uv } }); // top-right corner - if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Top)) + if (m_layout.horizontal_orientation == Layout::HO_Right || m_layout.vertical_orientation == Layout::VO_Top) GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); else GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_right_uv, internal_top_uv }, { right_uv, internal_top_uv }, { right_uv, top_uv }, { internal_right_uv, top_uv } }); @@ -1302,7 +1274,7 @@ void GLToolbar::render_background(float left, float top, float right, float bott GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_right_uv, internal_bottom_uv }, { right_uv, internal_bottom_uv }, { right_uv, internal_top_uv }, { internal_right_uv, internal_top_uv } }); // bottom-left corner - if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Bottom)) + if (m_layout.horizontal_orientation == Layout::HO_Left || m_layout.vertical_orientation == Layout::VO_Bottom) GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); else GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { left_uv, bottom_uv }, { internal_left_uv, bottom_uv }, { internal_left_uv, internal_bottom_uv }, { left_uv, internal_bottom_uv } }); @@ -1314,7 +1286,7 @@ void GLToolbar::render_background(float left, float top, float right, float bott GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, bottom_uv }, { internal_right_uv, bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } }); // bottom-right corner - if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Bottom)) + if (m_layout.horizontal_orientation == Layout::HO_Right || m_layout.vertical_orientation == Layout::VO_Bottom) GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); else GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_right_uv, bottom_uv }, { right_uv, bottom_uv }, { right_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv } }); @@ -1324,7 +1296,7 @@ void GLToolbar::render_background(float left, float top, float right, float bott void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighted_item) { // arrow texture not initialized - if (m_arrow_texture.texture.get_id() == 0) + if (m_arrow_texture.get_id() == 0) return; float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); @@ -1363,67 +1335,71 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte top -= separator_stride; float right = left + scaled_icons_size; - unsigned int tex_id = m_arrow_texture.texture.get_id(); + const unsigned int tex_id = m_arrow_texture.get_id(); // arrow width and height - float arr_tex_width = (float)m_arrow_texture.texture.get_width(); - float arr_tex_height = (float)m_arrow_texture.texture.get_height(); - if ((tex_id != 0) && (arr_tex_width > 0) && (arr_tex_height > 0)) { - float inv_tex_width = (arr_tex_width != 0.0f) ? 1.0f / arr_tex_width : 0.0f; - float inv_tex_height = (arr_tex_height != 0.0f) ? 1.0f / arr_tex_height : 0.0f; - + const float arr_tex_width = (float)m_arrow_texture.get_width(); + const float arr_tex_height = (float)m_arrow_texture.get_height(); + if (tex_id != 0 && arr_tex_width > 0.0f && arr_tex_height > 0.0f) { float internal_left = left + border - scaled_icons_size * 1.5f; // add scaled_icons_size for huge arrow float internal_right = right - border + scaled_icons_size * 1.5f; float internal_top = top - border; // bottom is not moving and should be calculated from arrow texture sides ratio - float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width(); + float arrow_sides_ratio = (float)m_arrow_texture.get_height() / (float)m_arrow_texture.get_width(); float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio ; - float internal_left_uv = (float)m_arrow_texture.metadata.left * inv_tex_width; - float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_width; - float internal_top_uv = 1.0f - (float)m_arrow_texture.metadata.top * inv_tex_height; - float internal_bottom_uv = (float)m_arrow_texture.metadata.bottom * inv_tex_height; + const float left_uv = 0.0f; + const float right_uv = 1.0f; + const float top_uv = 1.0f; + const float bottom_uv = 0.0f; - GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } }); + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { left_uv, top_uv }, { right_uv, top_uv }, { right_uv, bottom_uv }, { left_uv, bottom_uv } }); } } void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType type) { - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float factor = inv_zoom * m_layout.scale; + const Size cnv_size = parent.get_canvas_size(); + const float cnv_w = (float)cnv_size.get_width(); + const float cnv_h = (float)cnv_size.get_height(); - float scaled_icons_size = m_layout.icons_size * factor; - float scaled_separator_size = m_layout.separator_size * factor; - float scaled_gap_size = m_layout.gap_size * factor; - float scaled_border = m_layout.border * factor; - float scaled_width = get_width() * inv_zoom; - float scaled_height = get_height() * inv_zoom; + if (cnv_w == 0 || cnv_h == 0) + return; - float separator_stride = scaled_separator_size + scaled_gap_size; - float icon_stride = scaled_icons_size + scaled_gap_size; + const float inv_cnv_w = 1.0f / cnv_w; + const float inv_cnv_h = 1.0f / cnv_h; - float left = m_layout.left; - float top = m_layout.top; - float right = left + scaled_width; + const float icons_size_x = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_w; + const float icons_size_y = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_h; + const float separator_size = 2.0f * m_layout.separator_size * m_layout.scale * inv_cnv_w; + const float gap_size = 2.0f * m_layout.gap_size * m_layout.scale * inv_cnv_w; + const float border_w = 2.0f * m_layout.border * m_layout.scale * inv_cnv_w; + const float border_h = 2.0f * m_layout.border * m_layout.scale * inv_cnv_h; + const float width = 2.0f * get_width() * inv_cnv_w; + const float height = 2.0f * get_height() * inv_cnv_h; + + const float separator_stride = separator_size + gap_size; + const float icon_stride = icons_size_x + gap_size; + + float left = 2.0f * m_layout.left * inv_cnv_w; + float top = 2.0f * m_layout.top * inv_cnv_h; + float right = left + width; if (type == GLToolbarItem::SeparatorLine) - right = left + scaled_width * 0.5; - float bottom = top - scaled_height; + right = left + width * 0.5; + const float bottom = top - height; - render_background(left, top, right, bottom, scaled_border); + render_background(left, top, right, bottom, border_w, border_h); - left += scaled_border; - top -= scaled_border; + left += border_w; + top -= border_h; // renders icons - for (const GLToolbarItem* item : m_items) - { + for (const GLToolbarItem* item : m_items) { if (!item->is_visible()) continue; if (item->is_separator()) left += separator_stride; - else - { + else { //BBS GUI refactor item->render_left_pos = left; if (!item->is_action_with_text_image()) { @@ -1432,13 +1408,13 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType int tex_height = m_icons_texture.get_height(); if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); + item->render(tex_id, left, left + icons_size_x, top - icons_size_y, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); } //BBS: GUI refactor: GLToolbar if (item->is_action_with_text()) { - float scaled_text_size = item->get_extra_size_ratio() * scaled_icons_size; - item->render_text(left + scaled_icons_size, left + scaled_icons_size + scaled_text_size, top - scaled_icons_size, top); + float scaled_text_size = item->get_extra_size_ratio() * icons_size_x; + item->render_text(left + icons_size_x, left + icons_size_x + scaled_text_size, top - icons_size_y, top); left += scaled_text_size; } left += icon_stride; @@ -1448,28 +1424,37 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType void GLToolbar::render_vertical(const GLCanvas3D& parent) { - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float factor = inv_zoom * m_layout.scale; + const Size cnv_size = parent.get_canvas_size(); + const float cnv_w = (float)cnv_size.get_width(); + const float cnv_h = (float)cnv_size.get_height(); - float scaled_icons_size = m_layout.icons_size * factor; - float scaled_separator_size = m_layout.separator_size * factor; - float scaled_gap_size = m_layout.gap_size * factor; - float scaled_border = m_layout.border * factor; - float scaled_width = get_width() * inv_zoom; - float scaled_height = get_height() * inv_zoom; + if (cnv_w == 0 || cnv_h == 0) + return; - float separator_stride = scaled_separator_size + scaled_gap_size; - float icon_stride = scaled_icons_size + scaled_gap_size; + const float inv_cnv_w = 1.0f / cnv_w; + const float inv_cnv_h = 1.0f / cnv_h; - float left = m_layout.left; - float top = m_layout.top; - float right = left + scaled_width; - float bottom = top - scaled_height; + const float icons_size_x = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_w; + const float icons_size_y = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_h; + const float separator_size = 2.0f * m_layout.separator_size * m_layout.scale * inv_cnv_h; + const float gap_size = 2.0f * m_layout.gap_size * m_layout.scale * inv_cnv_h; + const float border_w = 2.0f * m_layout.border * m_layout.scale * inv_cnv_w; + const float border_h = 2.0f * m_layout.border * m_layout.scale * inv_cnv_h; + const float width = 2.0f * get_width() * inv_cnv_w; + const float height = 2.0f * get_height() * inv_cnv_h; - render_background(left, top, right, bottom, scaled_border); + const float separator_stride = separator_size + gap_size; + const float icon_stride = icons_size_y + gap_size; - left += scaled_border; - top -= scaled_border; + float left = 2.0f * m_layout.left * inv_cnv_w; + float top = 2.0f * m_layout.top * inv_cnv_h; + const float right = left + width; + const float bottom = top - height; + + render_background(left, top, right, bottom, border_w, border_h); + + left += border_w; + top -= border_h; // renders icons for (const GLToolbarItem* item : m_items) { @@ -1482,10 +1467,10 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) unsigned int tex_id; int tex_width, tex_height; if (item->is_action_with_text_image()) { - float scaled_text_size = m_layout.text_size * factor; - float scaled_text_width = item->get_extra_size_ratio() * scaled_icons_size; - float scaled_text_border = 2.5 * factor; - float scaled_text_height = scaled_icons_size / 2.0f; + float scaled_text_size = m_layout.text_size * m_layout.scale * inv_cnv_w; + float scaled_text_width = item->get_extra_size_ratio() * icons_size_x; + float scaled_text_border = 2.5 * m_layout.scale * inv_cnv_h; + float scaled_text_height = icons_size_y / 2.0f; item->render_text(left, left + scaled_text_size, top - scaled_text_border - scaled_text_height, top - scaled_text_border); float image_left = left + scaled_text_size; @@ -1494,7 +1479,7 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) tex_height = item->m_data.image_texture.get_height(); if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; - item->render_image(tex_id, image_left, image_left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); + item->render_image(tex_id, image_left, image_left + icons_size_x, top - icons_size_y, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); } else { tex_id = m_icons_texture.get_id(); @@ -1502,14 +1487,14 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) tex_height = m_icons_texture.get_height(); if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); + item->render(tex_id, left, left + icons_size_x, top - icons_size_y, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); //BBS: GUI refactor: GLToolbar } if (item->is_action_with_text()) { - float scaled_text_width = item->get_extra_size_ratio() * scaled_icons_size; - float scaled_text_height = scaled_icons_size; - item->render_text(left + scaled_icons_size, left + scaled_icons_size + scaled_text_width, top - scaled_text_height, top); + float scaled_text_width = item->get_extra_size_ratio() * icons_size_x; + float scaled_text_height = icons_size_y; + item->render_text(left + icons_size_x, left + icons_size_x + scaled_text_width, top - scaled_text_height, top); } top -= icon_stride; } diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 09da22d3cd..28ad69bb53 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2022 Enrico Turri @enricoturri1966, David Kocík @kocikdav, Oleksandra Iushchenko @YuSanka, Vojtěch Král @vojtechkral, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLToolbar_hpp_ #define slic3r_GLToolbar_hpp_ @@ -327,7 +331,7 @@ private: mutable GLTexture m_images_texture; mutable bool m_images_texture_dirty; BackgroundTexture m_background_texture; - BackgroundTexture m_arrow_texture; + GLTexture m_arrow_texture; Layout m_layout; ItemsList m_items; @@ -354,7 +358,7 @@ public: bool init(const BackgroundTexture::Metadata& background_texture); - bool init_arrow(const BackgroundTexture::Metadata& arrow_texture); + bool init_arrow(const std::string& filename); Layout::EType get_layout_type() const; void set_layout_type(Layout::EType type); @@ -436,8 +440,8 @@ private: int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; - void render_background(float left, float top, float right, float bottom, float border) const; - void render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType type); + void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const; + void render_horizontal(const GLCanvas3D &parent, GLToolbarItem::EType type); void render_vertical(const GLCanvas3D& parent); bool generate_icons_texture(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b9d3a8cd74..8e6c4f64a2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -56,6 +56,7 @@ #include "libslic3r/Thread.hpp" #include "libslic3r/miniz_extension.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" @@ -3233,8 +3234,7 @@ void GUI_App::set_label_clr_modified(const wxColour& clr) if (m_color_label_modified == clr) return; m_color_label_modified = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); app_config->save(); */ } @@ -3247,8 +3247,7 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) if (m_color_label_sys == clr) return; m_color_label_sys = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); app_config->save(); */ } @@ -5865,6 +5864,12 @@ Sidebar& GUI_App::sidebar() return plater_->sidebar(); } +GizmoObjectManipulation *GUI_App::obj_manipul() +{ + // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) + return (plater_ != nullptr) ? &plater_->get_view3D_canvas3D()->get_gizmos_manager().get_object_manipulation() : nullptr; +} + ObjectSettings* GUI_App::obj_settings() { return sidebar().obj_settings(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 2defd53a5d..7acf5e8f26 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -129,6 +129,7 @@ enum CameraMenuIDs { class Tab; class ConfigWizard; +class GizmoObjectManipulation; static wxString dots("...", wxConvUTF8); @@ -529,6 +530,7 @@ private: #endif /* __APPLE */ Sidebar& sidebar(); + GizmoObjectManipulation* obj_manipul(); ObjectSettings* obj_settings(); ObjectList* obj_list(); ObjectLayers* obj_layers(); diff --git a/src/slic3r/GUI/GUI_Colors.hpp b/src/slic3r/GUI/GUI_Colors.hpp index 32a0399181..0395e997c0 100644 --- a/src/slic3r/GUI/GUI_Colors.hpp +++ b/src/slic3r/GUI/GUI_Colors.hpp @@ -2,6 +2,7 @@ #define slic3r_GUI_Colors_hpp_ #include "imgui/imgui.h" +#include "libslic3r/Color.hpp" enum RenderCol_ { RenderCol_3D_Background = 0, @@ -38,13 +39,6 @@ public: static ImVec4 colors[RenderCol_Count]; }; const char* GetRenderColName(RenderCol idx); -inline std::array GLColor(ImVec4 color) { - return {color.x, color.y, color.z, color.w }; -} - -inline ImVec4 IMColor(std::array color) { - return ImVec4(color[0], color[1], color[2], color[3]); -} } diff --git a/src/slic3r/GUI/GUI_Geometry.cpp b/src/slic3r/GUI/GUI_Geometry.cpp new file mode 100644 index 0000000000..3cee023e12 --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.cpp @@ -0,0 +1,13 @@ +///|/ Copyright (c) Prusa Research 2021 Enrico Turri @enricoturri1966 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "libslic3r/libslic3r.h" +#include "GUI_Geometry.hpp" + +namespace Slic3r { +namespace GUI { + + +} // namespace Slic3r +} // namespace GUI diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp new file mode 100644 index 0000000000..ed89af649c --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -0,0 +1,82 @@ +///|/ Copyright (c) Prusa Research 2021 - 2023 Enrico Turri @enricoturri1966 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_GUI_Geometry_hpp_ +#define slic3r_GUI_Geometry_hpp_ + +namespace Slic3r { +namespace GUI { + +enum class ECoordinatesType : unsigned char +{ + World, + Instance, + Local +}; + +class TransformationType +{ +public: + enum Enum { + // Transforming in a world coordinate system + World = 0, + // Transforming in a instance coordinate system + Instance = 1, + // Transforming in a local coordinate system + Local = 2, + // Absolute transformations, allowed in local coordinate system only. + Absolute = 0, + // Relative transformations, allowed in both local and world coordinate system. + Relative = 4, + // For group selection, the transformation is performed as if the group made a single solid body. + Joint = 0, + // For group selection, the transformation is performed on each object independently. + Independent = 8, + + World_Relative_Joint = World | Relative | Joint, + World_Relative_Independent = World | Relative | Independent, + Instance_Absolute_Joint = Instance | Absolute | Joint, + Instance_Absolute_Independent = Instance | Absolute | Independent, + Instance_Relative_Joint = Instance | Relative | Joint, + Instance_Relative_Independent = Instance | Relative | Independent, + Local_Absolute_Joint = Local | Absolute | Joint, + Local_Absolute_Independent = Local | Absolute | Independent, + Local_Relative_Joint = Local | Relative | Joint, + Local_Relative_Independent = Local | Relative | Independent, + }; + + TransformationType() : m_value(World) {} + TransformationType(Enum value) : m_value(value) {} + TransformationType& operator=(Enum value) { m_value = value; return *this; } + + Enum operator()() const { return m_value; } + bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } + + void set_world() { this->remove(Instance); this->remove(Local); } + void set_instance() { this->remove(Local); this->add(Instance); } + void set_local() { this->remove(Instance); this->add(Local); } + void set_absolute() { this->remove(Relative); } + void set_relative() { this->add(Relative); } + void set_joint() { this->remove(Independent); } + void set_independent() { this->add(Independent); } + + bool world() const { return !this->has(Instance) && !this->has(Local); } + bool instance() const { return this->has(Instance); } + bool local() const { return this->has(Local); } + bool absolute() const { return !this->has(Relative); } + bool relative() const { return this->has(Relative); } + bool joint() const { return !this->has(Independent); } + bool independent() const { return this->has(Independent); } + +private: + void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } + void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } + + Enum m_value; +}; + +} // namespace Slic3r +} // namespace GUI + +#endif // slic3r_GUI_Geometry_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 24f44a12a1..bbe4de5ea5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,3 +1,9 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, David Kocík @kocikdav, Filip Sykala @Jony01, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2021 Mathias Rasmussen +///|/ Copyright (c) 2020 rongith +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r/libslic3r.h" #include "libslic3r/PresetBundle.hpp" #include "GUI_ObjectList.hpp" @@ -304,6 +310,7 @@ ObjectList::ObjectList(wxWindow* parent) : ObjectList::~ObjectList() { + delete m_objects_model; } void ObjectList::set_min_height() @@ -1949,7 +1956,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo const BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx); // First (any) GLVolume of the selected instance. They all share the same instance matrix. - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); const Geometry::Transformation inst_transform = v->get_instance_transformation(); const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse(); const Vec3d instance_offset = v->get_instance_offset(); @@ -2085,7 +2092,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode ModelVolume *new_volume = model_object.add_volume(std::move(mesh), type); // First (any) GLVolume of the selected instance. They all share the same instance matrix. - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); @@ -2810,7 +2817,7 @@ void ObjectList::merge(bool to_multipart_object) } } -void ObjectList::merge_volumes() +/*void ObjectList::merge_volumes() { std::vector obj_idxs, vol_idxs; get_selection_indexes(obj_idxs, vol_idxs); @@ -2838,11 +2845,11 @@ void ObjectList::merge_volumes() else { for (int vol_idx : vol_idxs) selection.add_volume(last_obj_idx, vol_idx, 0, false); - }*/ + }#1# #else wxGetApp().plater()->merge(obj_idxs[0], vol_idxs); #endif -} +}*/ void ObjectList::layers_editing() { @@ -3029,6 +3036,93 @@ bool ObjectList::can_split_instances() return selection.is_multiple_full_instance() || selection.is_single_full_instance(); } +bool ObjectList::has_selected_cut_object() const +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.IsEmpty()) + return false; + + for (wxDataViewItem item : sels) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + // ys_FIXME: The obj_idx= 0 && obj_idx < int(m_objects->size()) && object(obj_idx)->is_cut()) + return true; + } + + return false; +} + +void ObjectList::invalidate_cut_info_for_selection() +{ + const wxDataViewItem item = GetSelection(); + if (item) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0) + invalidate_cut_info_for_object(size_t(obj_idx)); + } +} + +void ObjectList::invalidate_cut_info_for_object(int obj_idx) +{ + ModelObject* init_obj = object(obj_idx); + if (!init_obj->is_cut()) + return; + + take_snapshot(_u8L("Invalidate cut info")); + + const CutObjectBase cut_id = init_obj->cut_id; + // invalidate cut for related objects (which have the same cut_id) + for (size_t idx = 0; idx < m_objects->size(); idx++) + if (ModelObject* obj = object(int(idx)); obj->cut_id.is_equal(cut_id)) { + obj->invalidate_cut(); + update_info_items(idx); + add_volumes_to_object_in_list(idx); + } + + update_lock_icons_for_model(); +} + +void ObjectList::delete_all_connectors_for_selection() +{ + const wxDataViewItem item = GetSelection(); + if (item) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0) + delete_all_connectors_for_object(size_t(obj_idx)); + } +} + +void ObjectList::delete_all_connectors_for_object(int obj_idx) +{ + ModelObject* init_obj = object(obj_idx); + if (!init_obj->is_cut()) + return; + + take_snapshot(_u8L("Delete all connectors")); + + const CutObjectBase cut_id = init_obj->cut_id; + // Delete all connectors for related objects (which have the same cut_id) + Model& model = wxGetApp().plater()->model(); + for (int idx = int(m_objects->size())-1; idx >= 0; idx--) + if (ModelObject* obj = object(idx); obj->cut_id.is_equal(cut_id)) { + obj->delete_connectors(); + + if (obj->volumes.empty() || !obj->has_solid_mesh()) { + model.delete_object(idx); + m_objects_model->Delete(m_objects_model->GetItemById(idx)); + continue; + } + + update_info_items(idx); + add_volumes_to_object_in_list(idx); + changed_object(int(idx)); + } + + update_lock_icons_for_model(); +} + bool ObjectList::can_merge_to_multipart_object() const { if (has_selected_cut_object()) @@ -3043,10 +3137,9 @@ bool ObjectList::can_merge_to_multipart_object() const return false; // should be selected just objects - for (wxDataViewItem item : sels) { + for (wxDataViewItem item : sels) if (!(m_objects_model->GetItemType(item) & (itObject | itInstance))) return false; - } return true; } @@ -3071,97 +3164,6 @@ bool ObjectList::can_mesh_boolean() const return (*m_objects)[obj_idx]->volumes.size() > 1 || ((*m_objects)[obj_idx]->volumes.size() == 1 && (*m_objects)[obj_idx]->volumes[0]->is_splittable()); } -bool ObjectList::has_selected_cut_object() const -{ - wxDataViewItemArray sels; - GetSelections(sels); - if (sels.IsEmpty()) - return false; - - for (wxDataViewItem item : sels) { - const int obj_idx = m_objects_model->GetObjectIdByItem(item); - if (obj_idx >= 0 && object(obj_idx)->is_cut()) - return true; - } - - return false; -} - -void ObjectList::invalidate_cut_info_for_selection() -{ - const wxDataViewItem item = GetSelection(); - if (item) { - const int obj_idx = m_objects_model->GetObjectIdByItem(item); - if (obj_idx >= 0) - invalidate_cut_info_for_object(size_t(obj_idx)); - } -} - -void ObjectList::invalidate_cut_info_for_object(int obj_idx) -{ - ModelObject *init_obj = object(obj_idx); - if (!init_obj->is_cut()) return; - - take_snapshot("Invalidate cut info"); - - const CutObjectBase cut_id = init_obj->cut_id; - // invalidate cut for related objects (which have the same cut_id) - for (size_t idx = 0; idx < m_objects->size(); idx++) - if (ModelObject *obj = object(int(idx)); obj->cut_id.is_equal(cut_id)) { - obj->invalidate_cut(); - update_info_items(idx); - add_volumes_to_object_in_list(idx); - } - - update_lock_icons_for_model(); -} - -void ObjectList::delete_all_connectors_for_selection() -{ - const wxDataViewItem item = GetSelection(); - if (item) { - const int obj_idx = m_objects_model->GetObjectIdByItem(item); - if (obj_idx >= 0) - delete_all_connectors_for_object(size_t(obj_idx)); - } -} - -void ObjectList::delete_all_connectors_for_object(int obj_idx) -{ - ModelObject *init_obj = object(obj_idx); - if (!init_obj->is_cut()) - return; - - take_snapshot("Delete all connectors"); - - auto has_solid_mesh = [](ModelObject* obj) { - for (const ModelVolume *volume : obj->volumes) - if (volume->is_model_part()) return true; - return false; - }; - - const CutObjectBase cut_id = init_obj->cut_id; - // Delete all connectors for related objects (which have the same cut_id) - Model &model = wxGetApp().plater()->model(); - for (int idx = int(m_objects->size()) - 1; idx >= 0; idx--) - if (ModelObject *obj = object(idx); obj->cut_id.is_equal(cut_id)) { - obj->delete_connectors(); - - if (obj->volumes.empty() || !has_solid_mesh(obj)) { - model.delete_object(idx); - m_objects_model->Delete(m_objects_model->GetItemById(idx)); - continue; - } - - update_info_items(idx); - add_volumes_to_object_in_list(idx); - changed_object(int(idx)); - } - - update_lock_icons_for_model(); -} - - // NO_PARAMETERS function call means that changed object index will be determine from Selection() void ObjectList::changed_object(const int obj_idx/* = -1*/) const { @@ -4349,7 +4351,7 @@ void ObjectList::update_selections() sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } else if (selection.is_single_volume() || selection.is_any_modifier()) { - const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const auto gl_vol = selection.get_first_volume(); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) return; } @@ -4425,8 +4427,7 @@ void ObjectList::update_selections() { if (m_selection_mode & smSettings) { - const auto idx = *selection.get_volume_idxs().begin(); - const auto gl_vol = selection.get_volume(idx); + const auto gl_vol = selection.get_first_volume(); if (gl_vol->volume_idx() >= 0) { // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids // are not associated with ModelVolumes, but they are temporarily generated by the backend diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 125c748037..8bd1e8fbdf 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -205,7 +205,7 @@ private: public: ObjectList(wxWindow* parent); - ~ObjectList(); + ~ObjectList() override; void set_min_height(); void update_min_height(); @@ -297,7 +297,7 @@ public: void del_info_item(const int obj_idx, InfoItemType type); void split(); void merge(bool to_multipart_object); - void merge_volumes(); // BBS: merge parts to single part + // void merge_volumes(); // BBS: merge parts to single part void layers_editing(); void boolean(); // BBS: Boolean Operation of parts diff --git a/src/slic3r/GUI/GUI_ObjectTable.cpp b/src/slic3r/GUI/GUI_ObjectTable.cpp index 3f194ec4e6..eda7c15b71 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.cpp +++ b/src/slic3r/GUI/GUI_ObjectTable.cpp @@ -2812,13 +2812,13 @@ int ObjectTablePanel::init_filaments_and_colors() } unsigned int i = 0; - unsigned char rgb[3]; + ColorRGB rgb; while (i < m_filaments_count) { const std::string& txt_color = global_config->opt_string("filament_colour", i); if (i < color_count) { - if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) + if (decode_color(txt_color, rgb)) { - m_filaments_colors[i] = wxColour(rgb[0], rgb[1], rgb[2]); + m_filaments_colors[i] = wxColour(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar()); } else { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index fc10e765e3..3c76e7d9d8 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -2,7 +2,6 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Layer.hpp" #include "IMSlider.hpp" -#include "IMSlider_Utils.hpp" #include "GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI.hpp" diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index bf2c4277b8..6fbec69ba7 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GUI_Utils_hpp_ #define slic3r_GUI_Utils_hpp_ @@ -23,6 +27,7 @@ #include "Event.hpp" #include "../libslic3r/libslic3r_version.h" #include "../libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" class wxCheckBox; @@ -39,28 +44,10 @@ inline int hex_to_int(const char c) return (c >= '0' && c <= '9') ? int(c - '0') : (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; } -static std::array decode_color_to_float_array(const std::string color) +static ColorRGBA decode_color_to_float_array(const std::string color) { - // set alpha to 1.0f by default - std::array ret = {0, 0, 0, 1.0f}; - const char * c = color.data() + 1; - if (color.size() == 7 && color.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_to_int(*c++); - int digit2 = hex_to_int(*c++); - if (digit1 == -1 || digit2 == -1) break; - ret[j] = float(digit1 * 16 + digit2) / 255.0f; - } - } - else if (color.size() == 9 && color.front() == '#') { - for (size_t j = 0; j < 4; ++j) { - int digit1 = hex_to_int(*c++); - int digit2 = hex_to_int(*c++); - if (digit1 == -1 || digit2 == -1) break; - - ret[j] = float(digit1 * 16 + digit2) / 255.0f; - } - } + ColorRGBA ret = ColorRGBA::BLACK(); + decode_color(color, ret); return ret; } @@ -470,14 +457,6 @@ public: std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics); -inline int hex_digit_to_int(const char c) -{ - return - (c >= '0' && c <= '9') ? int(c - '0') : - (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : - (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; -} - class TaskTimer { std::chrono::milliseconds start_timer; @@ -488,6 +467,16 @@ public: ~TaskTimer(); }; +class KeyAutoRepeatFilter +{ + size_t m_count{ 0 }; + +public: + void increase_count() { ++m_count; } + void reset_count() { m_count = 0; } + bool is_first() const { return m_count == 0; } +}; + /* Image Generator */ #define _3MF_COVER_SIZE wxSize(240, 240) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index ba97805013..be0c2e1518 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -17,6 +17,8 @@ #include +#include "slic3r/GUI/CameraUtils.hpp" + namespace Slic3r { namespace GUI { @@ -27,32 +29,12 @@ const int c_connectors_group_id = 4; const float UndefFloat = -999.f; // connector colors - -using ColorRGBA = std::array; - -static const ColorRGBA BLACK() { return {0.0f, 0.0f, 0.0f, 1.0f}; } -static const ColorRGBA BLUE() { return {0.0f, 0.0f, 1.0f, 1.0f}; } -static const ColorRGBA BLUEISH() { return {0.5f, 0.5f, 1.0f, 1.0f}; } -static const ColorRGBA CYAN() { return {0.0f, 1.0f, 1.0f, 1.0f}; } -static const ColorRGBA DARK_GRAY() { return {0.25f, 0.25f, 0.25f, 1.0f}; } -static const ColorRGBA DARK_YELLOW() { return {0.5f, 0.5f, 0.0f, 1.0f}; } -static const ColorRGBA GRAY() { return {0.5f, 0.5f, 0.5f, 1.0f}; } -static const ColorRGBA GREEN() { return {0.0f, 1.0f, 0.0f, 1.0f}; } -static const ColorRGBA GREENISH() { return {0.5f, 1.0f, 0.5f, 1.0f}; } -static const ColorRGBA LIGHT_GRAY() { return {0.75f, 0.75f, 0.75f, 1.0f}; } -static const ColorRGBA MAGENTA() { return {1.0f, 0.0f, 1.0f, 1.0f}; } -static const ColorRGBA ORANGE() { return {0.923f, 0.504f, 0.264f, 1.0f}; } -static const ColorRGBA RED() { return {1.0f, 0.0f, 0.0f, 1.0f}; } -static const ColorRGBA REDISH() { return {1.0f, 0.5f, 0.5f, 1.0f}; } -static const ColorRGBA YELLOW() { return {1.0f, 1.0f, 0.0f, 1.0f}; } -static const ColorRGBA WHITE() { return {1.0f, 1.0f, 1.0f, 1.0f}; } - -static const ColorRGBA PLAG_COLOR = YELLOW(); -static const ColorRGBA DOWEL_COLOR = DARK_YELLOW(); -static const ColorRGBA HOVERED_PLAG_COLOR = CYAN(); +static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW(); +static const ColorRGBA DOWEL_COLOR = ColorRGBA::DARK_YELLOW(); +static const ColorRGBA HOVERED_PLAG_COLOR = ColorRGBA::CYAN(); static const ColorRGBA HOVERED_DOWEL_COLOR = {0.0f, 0.5f, 0.5f, 1.0f}; -static const ColorRGBA SELECTED_PLAG_COLOR = GRAY(); -static const ColorRGBA SELECTED_DOWEL_COLOR = GRAY(); // DARK_GRAY(); +static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); +static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::GRAY(); // DARK_GRAY(); static const ColorRGBA CONNECTOR_DEF_COLOR = {1.0f, 1.0f, 1.0f, 0.5f}; static const ColorRGBA CONNECTOR_ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f}; static const ColorRGBA HOVERED_ERR_COLOR = {1.0f, 0.3f, 0.3f, 1.0f}; @@ -103,8 +85,8 @@ static void rotate_z_3d(std::array& verts, float radian_angle) const double GLGizmoAdvancedCut::Offset = 10.0; const double GLGizmoAdvancedCut::Margin = 20.0; -const std::array GLGizmoAdvancedCut::GrabberColor = { 1.0, 1.0, 0.0, 1.0 }; -const std::array GLGizmoAdvancedCut::GrabberHoverColor = { 0.7, 0.7, 0.0, 1.0}; +const ColorRGBA GLGizmoAdvancedCut::GrabberColor = { 1.0f, 1.0f, 0.0f, 1.0f }; +const ColorRGBA GLGizmoAdvancedCut::GrabberHoverColor = { 0.7f, 0.7f, 0.0f, 1.0f }; GLGizmoAdvancedCut::GLGizmoAdvancedCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoRotate3D(parent, icon_filename, sprite_id, nullptr) @@ -124,13 +106,19 @@ GLGizmoAdvancedCut::GLGizmoAdvancedCut(GLCanvas3D& parent, const std::string& ic for (int i = 0; i < 4; i++) m_cut_plane_points[i] = { 0., 0., 0. }; - set_group_id(m_gizmos.size()); + m_group_id = (m_gizmos.size()); m_rotation.setZero(); //m_current_base_rotation.setZero(); m_rotate_cmds.clear(); m_buffered_rotation.setZero(); } +void GLGizmoAdvancedCut::data_changed(bool is_serializing) +{ + GLGizmoRotate3D::data_changed(is_serializing); + finish_rotation(); +} + bool GLGizmoAdvancedCut::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down) { CutConnectors &connectors = m_c->selection_info()->model_object()->cut_connectors; @@ -146,7 +134,7 @@ bool GLGizmoAdvancedCut::gizmo_event(SLAGizmoEventType action, const Vec2d &mous return false; if (m_hover_id != -1) { - start_dragging(); + //start_dragging(); return true; } @@ -241,7 +229,7 @@ bool GLGizmoAdvancedCut::unproject_on_cut_plane(const Vec2d &mouse_pos, Vec3d &p Vec3d point; Vec3d direction; Vec3d hit; - MeshRaycaster::line_from_mouse_pos_static(mouse_pos, Transform3d::Identity(), camera, point, direction); + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); Vec3d normal = -cp->get_normal().cast(); double den = normal.dot(direction); if (den != 0.) { @@ -431,38 +419,32 @@ CommonGizmosDataID GLGizmoAdvancedCut::on_get_requirements() const void GLGizmoAdvancedCut::on_start_dragging() { - for (auto gizmo : m_gizmos) { - if (m_hover_id == gizmo.get_group_id()) { - gizmo.start_dragging(); - return; - } + if (m_hover_id == X || m_hover_id == Y || m_hover_id == Z) { + m_gizmos[m_hover_id].start_dragging(); + } else if (m_hover_id == c_connectors_group_id - 1) { + const Selection& selection = m_parent.get_selection(); + const BoundingBoxf3& box = selection.get_bounding_box(); + m_start_movement = m_movement; + m_start_height = m_height; + m_drag_pos = m_move_grabber.center; } - - if (m_hover_id != get_group_id()) - return; - - const Selection& selection = m_parent.get_selection(); - const BoundingBoxf3& box = selection.get_bounding_box(); - m_start_movement = m_movement; - m_start_height = m_height; - m_drag_pos = m_move_grabber.center; - - if (m_hover_id >= c_connectors_group_id) - Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Move connector"); } void GLGizmoAdvancedCut::on_stop_dragging() { if (m_hover_id == X || m_hover_id == Y || m_hover_id == Z) { + m_gizmos[m_hover_id].stop_dragging(); Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Rotate cut plane"); } else if (m_hover_id == c_connectors_group_id - 1) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Move cut plane"); + } else if (m_hover_id >= c_connectors_group_id) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Move connector"); } } -void GLGizmoAdvancedCut::on_update(const UpdateData& data) +void GLGizmoAdvancedCut::on_dragging(const UpdateData &data) { - GLGizmoRotate3D::on_update(data); + GLGizmoRotate3D::on_dragging(data); Vec3d rotation; for (int i = 0; i < 3; i++) @@ -475,7 +457,7 @@ void GLGizmoAdvancedCut::on_update(const UpdateData& data) m_rotation = rotation; //m_move_grabber.angles = m_current_base_rotation + m_rotation; - if (m_hover_id == get_group_id()) { + if (m_hover_id == m_group_id) { double move = calc_projection(data.mouse_ray); set_movement(m_start_movement + move); Vec3d plane_normal = get_plane_normal(); @@ -511,7 +493,7 @@ void GLGizmoAdvancedCut::on_render() render_cut_line(); } - +/* void GLGizmoAdvancedCut::on_render_for_picking() { GLGizmoRotate3D::on_render_for_picking(); @@ -525,12 +507,17 @@ void GLGizmoAdvancedCut::on_render_for_picking() float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); #endif - std::array color = picking_color_component(0); - m_move_grabber.color[0] = color[0]; - m_move_grabber.color[1] = color[1]; - m_move_grabber.color[2] = color[2]; - m_move_grabber.color[3] = color[3]; - m_move_grabber.render_for_picking(mean_size); + m_move_grabber.color = picking_color_component(0); + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + const Camera &camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + m_move_grabber.render_for_picking(mean_size); + + shader->stop_using(); + } glsafe(::glEnable(GL_DEPTH_TEST)); auto inst_id = m_c->selection_info()->get_active_instance(); @@ -548,7 +535,6 @@ void GLGizmoAdvancedCut::on_render_for_picking() Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); float height = connector.height; - const Camera &camera = wxGetApp().plater()->get_camera(); if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { pos -= height * m_cut_plane_normal; height *= 2; @@ -561,14 +547,13 @@ void GLGizmoAdvancedCut::on_render_for_picking() Transform3d scale_tf = Transform3d::Identity(); scale_tf.scale(Vec3f(connector.radius, connector.radius, height).cast()); - const Transform3d view_model_matrix = translate_tf * m_rotate_matrix * scale_tf; + const Transform3d model_matrix = translate_tf * m_rotate_matrix * scale_tf; - - std::array color = picking_color_component(i+1); - render_connector_model(m_shapes[connectors[i].attribs], color, view_model_matrix, true); + ColorRGBA color = picking_color_component(i+1); + render_connector_model(m_shapes[connectors[i].attribs], color, model_matrix, true); } } - +*/ void GLGizmoAdvancedCut::on_render_input_window(float x, float y, float bottom_limit) { GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); @@ -638,7 +623,7 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoAdvancedCut: Invalid object selection"); // m_cut_z is the distance from the bed. Subtract possible SLA elevation. - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* first_glvolume = selection.get_first_volume(); // perform cut { @@ -882,67 +867,117 @@ void GLGizmoAdvancedCut::render_cut_plane_and_grabbers() point += object_offset; } - // draw plane glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - for (const Vec3d& point : plane_points_rot) { - ::glVertex3f(point(0), point(1), point(2)); - } - glsafe(::glEnd()); + // draw plane + { + m_plane.reset(); - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; + init_data.reserve_vertices(4); + init_data.reserve_vertices(6); - // Draw the grabber and the connecting line - Vec3d plane_center_rot = calc_plane_center(plane_points_rot); - m_move_grabber.center = plane_center_rot + plane_normal_rot * Offset; - // m_move_grabber.angles = m_current_base_rotation + m_rotation; + // vertices + for (const Vec3d &point : plane_points_rot) { + init_data.add_vertex((Vec3f)point.cast()); + } - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); - glsafe(::glColor3f(1.0, 1.0, 0.0)); - glLineStipple(1, 0x0FFF); - glEnable(GL_LINE_STIPPLE); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center_rot.data()); - ::glVertex3dv(m_move_grabber.center.data()); - glsafe(::glEnd()); - glDisable(GL_LINE_STIPPLE); + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); - // std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_move_grabber.color); - // m_move_grabber.color = GrabberColor; - // m_move_grabber.hover_color = GrabberHoverColor; - // m_move_grabber.render(m_hover_id == get_group_id(), (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0)); - bool hover = (m_hover_id == get_group_id()); - std::array render_color; - if (hover) { - render_color = GrabberHoverColor; - } - else - render_color = GrabberColor; + m_plane.init_from(std::move(init_data)); + } + const Camera &camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + m_plane.render(); - const GLModel &cube = m_move_grabber.get_cube(); - // BBS set to fixed size grabber - // float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - float fullsize = 8.0f; - if (GLGizmoBase::INV_ZOOM > 0) { - fullsize = m_move_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM; + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); + + // Draw the grabber and the connecting line + Vec3d plane_center_rot = calc_plane_center(plane_points_rot); + m_move_grabber.center = plane_center_rot + plane_normal_rot * Offset; + // m_move_grabber.angles = m_current_base_rotation + m_rotation; + + { + m_grabber_connection.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_vertices(2); + + // vertices + init_data.add_vertex((Vec3f)plane_center_rot.cast()); + init_data.add_vertex((Vec3f)m_move_grabber.center.cast()); + + // indices + init_data.add_line(0, 1); + + m_grabber_connection.init_from(std::move(init_data)); + } + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); + glLineStipple(1, 0x0FFF); + glEnable(GL_LINE_STIPPLE); + m_grabber_connection.render(); + glDisable(GL_LINE_STIPPLE); + + shader->stop_using(); } - const_cast(&cube)->set_color(-1, render_color); + { + GLShaderProgram *shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + // std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_move_grabber.color); + // m_move_grabber.color = GrabberColor; + // m_move_grabber.hover_color = GrabberHoverColor; + // m_move_grabber.render(m_hover_id == get_group_id(), (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0)); + bool hover = (m_hover_id == m_group_id); + ColorRGBA render_color; + if (hover) { + render_color = GrabberHoverColor; + } + else + render_color = GrabberColor; - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_move_grabber.center.x(), m_move_grabber.center.y(), m_move_grabber.center.z())); - glsafe(::glMultMatrixd(m_rotate_matrix.data())); + PickingModel &cube = m_move_grabber.get_cube(); + // BBS set to fixed size grabber + // float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); + float fullsize = 8.0f; + if (GLGizmoBase::INV_ZOOM > 0) { + fullsize = m_move_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM; + } - glsafe(::glScaled(fullsize, fullsize, fullsize)); - cube.render(); - glsafe(::glPopMatrix()); + cube.model.set_color(render_color); + + const Transform3d trafo_matrix = Geometry::assemble_transform(m_move_grabber.center) * m_rotate_matrix * + Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), fullsize * Vec3d::Ones()); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + cube.model.render(); + shader->stop_using(); + } // Should be placed at last, because GLGizmoRotate3D clears depth buffer set_center(m_cut_plane_center); @@ -1007,9 +1042,9 @@ void GLGizmoAdvancedCut::render_connectors() Transform3d scale_tf = Transform3d::Identity(); scale_tf.scale(Vec3f(connector.radius, connector.radius, height).cast()); - const Transform3d view_model_matrix = translate_tf * m_rotate_matrix * scale_tf; + const Transform3d model_matrix = translate_tf * m_rotate_matrix * scale_tf; - render_connector_model(m_shapes[connector.attribs], render_color, view_model_matrix); + render_connector_model(m_shapes[connector.attribs], render_color, model_matrix); } } @@ -1025,36 +1060,65 @@ void GLGizmoAdvancedCut::render_cut_line() if (!cut_line_processing() || m_cut_line_end == Vec3d::Zero()) return; - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glColor3f(0.0, 1.0, 0.0)); - glEnable(GL_LINE_STIPPLE); - ::glBegin(GL_LINES); - ::glVertex3dv(m_cut_line_begin.data()); - ::glVertex3dv(m_cut_line_end.data()); - glsafe(::glEnd()); - glDisable(GL_LINE_STIPPLE); + glsafe(::glDisable(GL_DEPTH_TEST)); + + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + { + m_cut_line.reset(); + + GLModel::Geometry init_data; + init_data.format = {GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + init_data.color = ColorRGBA::GREEN(); + init_data.reserve_vertices(2); + init_data.reserve_vertices(2); + + // vertices + init_data.add_vertex((Vec3f) m_cut_line_begin.cast()); + init_data.add_vertex((Vec3f) m_cut_line_end.cast()); + + // indices + init_data.add_line(0, 1); + + m_cut_line.init_from(std::move(init_data)); + } + const Camera &camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + glEnable(GL_LINE_STIPPLE); + glLineStipple(1, 0x0FFF); + m_cut_line.render(); + glDisable(GL_LINE_STIPPLE); + + shader->stop_using(); + } } -void GLGizmoAdvancedCut::render_connector_model(GLModel &model, const std::array &color, Transform3d view_model_matrix, bool for_picking) +void GLGizmoAdvancedCut::render_connector_model(GLModel &model, const ColorRGBA &color, Transform3d model_matrix, bool for_picking) { - glPushMatrix(); GLShaderProgram *shader = nullptr; if (for_picking) - shader = wxGetApp().get_shader("cali"); + shader = wxGetApp().get_shader("flat"); else shader = wxGetApp().get_shader("gouraud_light"); if (shader) { shader->start_using(); - glsafe(::glMultMatrixd(view_model_matrix.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); - model.set_color(-1, color); + model.set_color(color); model.render(); shader->stop_using(); } - glPopMatrix(); } void GLGizmoAdvancedCut::clear_selection() @@ -1814,7 +1878,7 @@ bool GLGizmoAdvancedCut::process_cut_line(SLAGizmoEventType action, const Vec2d Vec3d pt; Vec3d dir; - MeshRaycaster::line_from_mouse_pos_static(mouse_position, Transform3d::Identity(), camera, pt, dir); + CameraUtils::ray_from_screen_pos(camera, mouse_position, pt, dir); dir.normalize(); pt += dir; // Move the pt along dir so it is not clipped. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp index 5cb9e765cc..d791663ea9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp @@ -27,8 +27,8 @@ struct Rotate_data { private: static const double Offset; static const double Margin; - static const std::array GrabberColor; - static const std::array GrabberHoverColor; + static const ColorRGBA GrabberColor; + static const ColorRGBA GrabberHoverColor; mutable double m_movement; mutable double m_height; // height of cut plane to heatbed @@ -53,6 +53,9 @@ private: bool m_place_on_cut_lower{false}; bool m_rotate_upper{false}; bool m_rotate_lower{false}; + GLModel m_plane; + GLModel m_grabber_connection; + GLModel m_cut_line; bool m_do_segment; double m_segment_smoothing_alpha; @@ -135,45 +138,24 @@ public: virtual bool apply_clipping_plane() { return m_connectors_editing; } + void data_changed(bool is_serializing) override; + protected: - virtual bool on_init(); - virtual void on_load(cereal::BinaryInputArchive &ar) override; - virtual void on_save(cereal::BinaryOutputArchive &ar) const override; - virtual std::string on_get_name() const; - virtual void on_set_state(); - virtual bool on_is_activable() const; - virtual CommonGizmosDataID on_get_requirements() const override; - virtual void on_start_dragging() override; - virtual void on_stop_dragging() override; - virtual void on_update(const UpdateData& data); - virtual void on_render(); - virtual void on_render_for_picking(); + bool on_init() override; + void on_load(cereal::BinaryInputArchive &ar) override; + void on_save(cereal::BinaryOutputArchive &ar) const override; + std::string on_get_name() const override; + void on_set_state() override; + bool on_is_activable() const override; + CommonGizmosDataID on_get_requirements() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_dragging(const UpdateData& data) override; + void on_render() override; virtual void on_render_input_window(float x, float y, float bottom_limit); void show_tooltip_information(float x, float y); - virtual void on_enable_grabber(unsigned int id) - { - if (id < 3) - m_gizmos[id].enable_grabber(0); - else if (id == 3) - this->enable_grabber(0); - } - - virtual void on_disable_grabber(unsigned int id) - { - if (id < 3) - m_gizmos[id].disable_grabber(0); - else if (id == 3) - this->disable_grabber(0); - } - - virtual void on_set_hover_id() - { - for (int i = 0; i < 3; ++i) - m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); - } - private: void perform_cut(const Selection& selection); bool can_perform_cut() const; @@ -201,7 +183,7 @@ private: void render_connectors(); void render_clipper_cut(); void render_cut_line(); - void render_connector_model(GLModel &model, const std::array& color, Transform3d view_model_matrix, bool for_picking = false); + void render_connector_model(GLModel &model, const ColorRGBA& color, Transform3d model_matrix, bool for_picking = false); void clear_selection(); void init_connector_shapes(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index b1d98d3dc2..ed98b81931 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -1,9 +1,14 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoBase.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_Colors.hpp" // TODO: Display tooltips quicker on Linux @@ -21,73 +26,63 @@ const float GLGizmoBase::Grabber::FixedGrabberSize = 16.0f; const float GLGizmoBase::Grabber::FixedRadiusSize = 80.0f; -std::array GLGizmoBase::DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; -std::array GLGizmoBase::DEFAULT_DRAG_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; -std::array GLGizmoBase::DEFAULT_HIGHLIGHT_COLOR = { 1.0f, 0.38f, 0.0f, 1.0f }; -std::array, 3> GLGizmoBase::AXES_HOVER_COLOR = {{ +ColorRGBA GLGizmoBase::DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; +ColorRGBA GLGizmoBase::DEFAULT_DRAG_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; +ColorRGBA GLGizmoBase::DEFAULT_HIGHLIGHT_COLOR = {1.0f, 0.38f, 0.0f, 1.0f}; +std::array GLGizmoBase::AXES_HOVER_COLOR = {{ { 0.7f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.7f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.7f, 1.0f } }}; -std::array, 3> GLGizmoBase::AXES_COLOR = { { +std::array GLGizmoBase::AXES_COLOR = {{ { 1.0, 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }}; -std::array GLGizmoBase::CONSTRAINED_COLOR = { 0.5f, 0.5f, 0.5f, 1.0f }; -std::array GLGizmoBase::FLATTEN_COLOR = { 0.96f, 0.93f, 0.93f, 0.5f }; -std::array GLGizmoBase::FLATTEN_HOVER_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; +ColorRGBA GLGizmoBase::CONSTRAINED_COLOR = {0.5f, 0.5f, 0.5f, 1.0f}; +ColorRGBA GLGizmoBase::FLATTEN_COLOR = {0.96f, 0.93f, 0.93f, 0.5f}; +ColorRGBA GLGizmoBase::FLATTEN_HOVER_COLOR = {1.0f, 1.0f, 1.0f, 0.75f}; // new style color -std::array GLGizmoBase::GRABBER_NORMAL_COL = {1.0f, 1.0f, 1.0f, 1.0f}; -std::array GLGizmoBase::GRABBER_HOVER_COL = {0.863f, 0.125f, 0.063f, 1.0f}; -std::array GLGizmoBase::GRABBER_UNIFORM_COL = {0, 1.0, 1.0, 1.0f}; -std::array GLGizmoBase::GRABBER_UNIFORM_HOVER_COL = {0, 0.7, 0.7, 1.0f}; +ColorRGBA GLGizmoBase::GRABBER_NORMAL_COL = {1.0f, 1.0f, 1.0f, 1.0f}; +ColorRGBA GLGizmoBase::GRABBER_HOVER_COL = {0.863f, 0.125f, 0.063f, 1.0f}; +ColorRGBA GLGizmoBase::GRABBER_UNIFORM_COL = {0, 1.0, 1.0, 1.0f}; +ColorRGBA GLGizmoBase::GRABBER_UNIFORM_HOVER_COL = {0, 0.7, 0.7, 1.0f}; void GLGizmoBase::update_render_colors() { GLGizmoBase::AXES_COLOR = { { - GLColor(RenderColor::colors[RenderCol_Grabber_X]), - GLColor(RenderColor::colors[RenderCol_Grabber_Y]), - GLColor(RenderColor::colors[RenderCol_Grabber_Z]) + ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Grabber_X]), + ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Grabber_Y]), + ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Grabber_Z]) } }; - GLGizmoBase::FLATTEN_COLOR = GLColor(RenderColor::colors[RenderCol_Flatten_Plane]); - GLGizmoBase::FLATTEN_HOVER_COLOR = GLColor(RenderColor::colors[RenderCol_Flatten_Plane_Hover]); + GLGizmoBase::FLATTEN_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Flatten_Plane]); + GLGizmoBase::FLATTEN_HOVER_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Flatten_Plane_Hover]); } void GLGizmoBase::load_render_colors() { - RenderColor::colors[RenderCol_Grabber_X] = IMColor(GLGizmoBase::AXES_COLOR[0]); - RenderColor::colors[RenderCol_Grabber_Y] = IMColor(GLGizmoBase::AXES_COLOR[1]); - RenderColor::colors[RenderCol_Grabber_Z] = IMColor(GLGizmoBase::AXES_COLOR[2]); - RenderColor::colors[RenderCol_Flatten_Plane] = IMColor(GLGizmoBase::FLATTEN_COLOR); - RenderColor::colors[RenderCol_Flatten_Plane_Hover] = IMColor(GLGizmoBase::FLATTEN_HOVER_COLOR); + RenderColor::colors[RenderCol_Grabber_X] = ImGuiWrapper::to_ImVec4(GLGizmoBase::AXES_COLOR[0]); + RenderColor::colors[RenderCol_Grabber_Y] = ImGuiWrapper::to_ImVec4(GLGizmoBase::AXES_COLOR[1]); + RenderColor::colors[RenderCol_Grabber_Z] = ImGuiWrapper::to_ImVec4(GLGizmoBase::AXES_COLOR[2]); + RenderColor::colors[RenderCol_Flatten_Plane] = ImGuiWrapper::to_ImVec4(GLGizmoBase::FLATTEN_COLOR); + RenderColor::colors[RenderCol_Flatten_Plane_Hover] = ImGuiWrapper::to_ImVec4(GLGizmoBase::FLATTEN_HOVER_COLOR); } -GLGizmoBase::Grabber::Grabber() - : center(Vec3d::Zero()) - , angles(Vec3d::Zero()) - , dragging(false) - , enabled(true) -{ - color = GRABBER_NORMAL_COL; - hover_color = GRABBER_HOVER_COL; -} +PickingModel GLGizmoBase::Grabber::s_cube; +PickingModel GLGizmoBase::Grabber::s_cone; -void GLGizmoBase::Grabber::render(bool hover, float size) const +GLGizmoBase::Grabber::~Grabber() { - std::array render_color; - if (hover) { - render_color = hover_color; - } - else - render_color = color; + if (s_cube.model.is_initialized()) + s_cube.model.reset(); - render(size, render_color, false); + if (s_cone.model.is_initialized()) + s_cone.model.reset(); } float GLGizmoBase::Grabber::get_half_size(float size) const @@ -100,70 +95,124 @@ float GLGizmoBase::Grabber::get_dragging_half_size(float size) const return get_half_size(size) * DraggingScaleFactor; } -const GLModel& GLGizmoBase::Grabber::get_cube() const +PickingModel &GLGizmoBase::Grabber::get_cube() { - if (! cube_initialized) { + if (!s_cube.model.is_initialized()) { // This cannot be done in constructor, OpenGL is not yet // initialized at that point (on Linux at least). - indexed_triangle_set mesh = its_make_cube(1., 1., 1.); - its_translate(mesh, Vec3f(-0.5, -0.5, -0.5)); - const_cast(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); - const_cast(cube_initialized) = true; + indexed_triangle_set its = its_make_cube(1.0, 1.0, 1.0); + its_translate(its, -0.5f * Vec3f::Ones()); + s_cube.model.init_from(its); + s_cube.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } - return cube; + return s_cube; } -void GLGizmoBase::Grabber::render(float size, const std::array& render_color, bool picking) const +void GLGizmoBase::Grabber::register_raycasters_for_picking(int id) { - if (! cube_initialized) { + picking_id = id; + // registration will happen on next call to render() +} + +void GLGizmoBase::Grabber::unregister_raycasters_for_picking() +{ + wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, picking_id); + picking_id = -1; + raycasters = { nullptr }; +} + +void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color) +{ + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader == nullptr) + return; + + if (!s_cube.model.is_initialized()) { // This cannot be done in constructor, OpenGL is not yet // initialized at that point (on Linux at least). - indexed_triangle_set mesh = its_make_cube(1., 1., 1.); - its_translate(mesh, Vec3f(-0.5, -0.5, -0.5)); - const_cast(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); - const_cast(cube_initialized) = true; + indexed_triangle_set its = its_make_cube(1.0, 1.0, 1.0); + its_translate(its, -0.5f * Vec3f::Ones()); + s_cube.model.init_from(its); + s_cube.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } + + if (!s_cone.model.is_initialized()) { + indexed_triangle_set its = its_make_cone(1.0, 1.0, double(PI) / 18.0); + s_cone.model.init_from(its); + s_cone.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } //BBS set to fixed size grabber - //float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - float fullsize = 8.0f; - if (GLGizmoBase::INV_ZOOM > 0) { - fullsize = FixedGrabberSize * GLGizmoBase::INV_ZOOM; + const float grabber_size = FixedGrabberSize * INV_ZOOM; + const double extension_size = 0.75 * FixedGrabberSize * INV_ZOOM; + + s_cube.model.set_color(render_color); + s_cone.model.set_color(render_color); + + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Matrix3d view_matrix_no_offset = view_matrix.matrix().block(0, 0, 3, 3); + + auto render_extension = [&view_matrix, &view_matrix_no_offset, shader, this](int idx, PickingModel &model, const Transform3d &model_matrix) { + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix_no_offset * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + model.model.render(); + + if (raycasters[idx] == nullptr) { + GLCanvas3D &canvas = *wxGetApp().plater()->canvas3D(); + raycasters[idx] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *model.mesh_raycaster, model_matrix); + } else { + raycasters[idx]->set_transform(model_matrix); + } + }; + + if (extensions == EGrabberExtension::PosZ) { + const Transform3d model_matrix = matrix * Geometry::assemble_transform(center, angles, Vec3d(0.75 * extension_size, 0.75 * extension_size, 2.0 * extension_size)); + render_extension(0, s_cone, model_matrix); + } else { + const Transform3d model_matrix = matrix * Geometry::assemble_transform(center, angles, grabber_size * Vec3d::Ones()); + render_extension(0, s_cube, model_matrix); + + const Transform3d extension_model_matrix_base = matrix * Geometry::assemble_transform(center, angles); + const Vec3d extension_scale(0.75 * extension_size, 0.75 * extension_size, 3.0 * extension_size); + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0) { + render_extension(1, s_cone, extension_model_matrix_base * Geometry::assemble_transform(2.0 * extension_size * Vec3d::UnitX(), Vec3d(0.0, 0.5 * double(PI), 0.0), extension_scale)); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0) { + render_extension(2, s_cone, extension_model_matrix_base * Geometry::assemble_transform(-2.0 * extension_size * Vec3d::UnitX(), Vec3d(0.0, -0.5 * double(PI), 0.0), extension_scale)); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0) { + render_extension(3, s_cone, extension_model_matrix_base * Geometry::assemble_transform(2.0 * extension_size * Vec3d::UnitY(), Vec3d(-0.5 * double(PI), 0.0, 0.0), extension_scale)); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0) { + render_extension(4, s_cone, extension_model_matrix_base * Geometry::assemble_transform(-2.0 * extension_size * Vec3d::UnitY(), Vec3d(0.5 * double(PI), 0.0, 0.0), extension_scale)); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0) { + render_extension(5, s_cone, extension_model_matrix_base * Geometry::assemble_transform(2.0 * extension_size * Vec3d::UnitZ(), Vec3d::Zero(), extension_scale)); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0) { + render_extension(6, s_cone, extension_model_matrix_base * Geometry::assemble_transform(-2.0 * extension_size * Vec3d::UnitZ(), Vec3d(double(PI), 0.0, 0.0), extension_scale)); + } } - - - const_cast(&cube)->set_color(-1, render_color); - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(center.x(), center.y(), center.z())); - glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); - glsafe(::glScaled(fullsize, fullsize, fullsize)); - cube.render(); - glsafe(::glPopMatrix()); } - GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : m_parent(parent) , m_group_id(-1) , m_state(Off) - , m_shortcut_key(0) + , m_shortcut_key(NO_SHORTCUT_KEY_VALUE) , m_icon_filename(icon_filename) , m_sprite_id(sprite_id) - , m_hover_id(-1) - , m_dragging(false) , m_imgui(wxGetApp().imgui()) - , m_first_input_window_render(true) - , m_dirty(false) { - m_base_color = DEFAULT_BASE_COLOR; - m_drag_color = DEFAULT_DRAG_COLOR; - m_highlight_color = DEFAULT_HIGHLIGHT_COLOR; - m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24)); - m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.)); - m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.)); +} + + +std::string GLGizmoBase::get_action_snapshot_name() const +{ + return "Gizmo action"; } void GLGizmoBase::set_icon_filename(const std::string &filename) { @@ -172,64 +221,15 @@ void GLGizmoBase::set_icon_filename(const std::string &filename) { void GLGizmoBase::set_hover_id(int id) { - if (m_grabbers.empty() || (id < (int)m_grabbers.size())) - { - m_hover_id = id; - on_set_hover_id(); - } -} + // do not change hover id during dragging + assert(!m_dragging); -void GLGizmoBase::set_highlight_color(const std::array& color) -{ - m_highlight_color = color; -} - -void GLGizmoBase::enable_grabber(unsigned int id) -{ - if (id < m_grabbers.size()) - m_grabbers[id].enabled = true; - - on_enable_grabber(id); -} - -void GLGizmoBase::disable_grabber(unsigned int id) -{ - if (id < m_grabbers.size()) - m_grabbers[id].enabled = false; - - on_disable_grabber(id); -} - -void GLGizmoBase::start_dragging() -{ - m_dragging = true; - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - m_grabbers[i].dragging = (m_hover_id == i); - } - - on_start_dragging(); - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("this %1%, m_hover_id=%2%\n")%this %m_hover_id; -} - -void GLGizmoBase::stop_dragging() -{ - m_dragging = false; - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - m_grabbers[i].dragging = false; - } - - on_stop_dragging(); - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("this %1%, m_hover_id=%2%\n")%this %m_hover_id; -} - -void GLGizmoBase::update(const UpdateData& data) -{ - if (m_hover_id != -1) - on_update(data); + // allow empty grabbers when not using grabbers but use hover_id - flatten, rotate +// if (!m_grabbers.empty() && id >= (int) m_grabbers.size()) +// return; + + m_hover_id = id; + on_set_hover_id(); } bool GLGizmoBase::update_items_state() @@ -237,7 +237,7 @@ bool GLGizmoBase::update_items_state() bool res = m_dirty; m_dirty = false; return res; -}; +} bool GLGizmoBase::GizmoImguiBegin(const std::string &name, int flags) { @@ -265,22 +265,18 @@ void GLGizmoBase::GizmoImguiSetNextWIndowPos(float &x, float y, int flag, float m_imgui->set_next_window_pos(x, y, flag, pivot_x, pivot_y); } -std::array GLGizmoBase::picking_color_component(unsigned int id) const +void GLGizmoBase::register_grabbers_for_picking() { - static const float INV_255 = 1.0f / 255.0f; + for (size_t i = 0; i < m_grabbers.size(); ++i) { + m_grabbers[i].register_raycasters_for_picking((m_group_id >= 0) ? m_group_id : i); + } +} - id = BASE_ID - id; - - if (m_group_id > -1) - id -= m_group_id; - - // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() - return std::array { - float((id >> 0) & 0xff) * INV_255, // red - float((id >> 8) & 0xff) * INV_255, // green - float((id >> 16) & 0xff) * INV_255, // blue - float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 // checksum for validating against unwanted alpha blending and multi sampling - }; +void GLGizmoBase::unregister_grabbers_for_picking() +{ + for (size_t i = 0; i < m_grabbers.size(); ++i) { + m_grabbers[i].unregister_raycasters_for_picking(); + } } void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const @@ -293,34 +289,101 @@ void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const } void GLGizmoBase::render_grabbers(float size) const +{ + render_grabbers(0, m_grabbers.size() - 1, size, false); +} + +void GLGizmoBase::render_grabbers(size_t first, size_t last, float size, bool force_hover) const { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - for (int i = 0; i < (int)m_grabbers.size(); ++i) { + glsafe(::glDisable(GL_CULL_FACE)); + for (size_t i = first; i <= last; ++i) { if (m_grabbers[i].enabled) - m_grabbers[i].render(m_hover_id == i, size); + m_grabbers[i].render(force_hover ? true : m_hover_id == (int)i, size); } + glsafe(::glEnable(GL_CULL_FACE)); shader->stop_using(); } -void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const -{ -#if ENABLE_FIXED_GRABBER - float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif +// help function to process grabbers +// call start_dragging, stop_dragging, on_dragging +bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { + bool is_dragging_finished = false; + if (mouse_event.Moving()) { + // it should not happen but for sure + assert(!m_dragging); + if (m_dragging) is_dragging_finished = true; + else return false; + } - for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { - if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); - m_grabbers[i].color = color; - m_grabbers[i].render_for_picking(mean_size); + if (mouse_event.LeftDown()) { + Selection &selection = m_parent.get_selection(); + if (!selection.is_empty() && m_hover_id != -1 /* && + (m_grabbers.empty() || m_hover_id < static_cast(m_grabbers.size()))*/) { + selection.setup_cache(); + + m_dragging = true; + for (auto &grabber : m_grabbers) grabber.dragging = false; +// if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) +// m_grabbers[m_hover_id].dragging = true; + + on_start_dragging(); + + // Let the plater know that the dragging started + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED)); + m_parent.set_as_dirty(); + return true; + } + } else if (m_dragging) { + // when mouse cursor leave window than finish actual dragging operation + bool is_leaving = mouse_event.Leaving(); + if (mouse_event.Dragging()) { + Point mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + auto ray = m_parent.mouse_ray(mouse_coord); + UpdateData data(ray, mouse_coord); + + on_dragging(data); + + wxGetApp().obj_manipul()->set_dirty(); + m_parent.set_as_dirty(); + return true; + } + else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { + do_stop_dragging(is_leaving); + return true; } } + return false; +} + +void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) +{ + for (auto& grabber : m_grabbers) grabber.dragging = false; + m_dragging = false; + + // NOTE: This should be part of GLCanvas3D + // Reset hover_id when leave window + if (perform_mouse_cleanup) m_parent.mouse_up_cleanup(); + + on_stop_dragging(); + + // There is prediction that after draggign, data are changed + // Data are updated twice also by canvas3D::reload_scene. + // Should be fixed. + m_parent.get_gizmos_manager().update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + + // Let the plater know that the dragging finished, so a delayed + // refresh of the scene with the background processing data should + // be performed. + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + m_parent.refresh_camera_scene_box(); } std::string GLGizmoBase::format(float value, unsigned int decimals) const @@ -336,9 +399,12 @@ void GLGizmoBase::render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); if (m_first_input_window_render) { - // for some reason, the imgui dialogs are not shown on screen in the 1st frame where they are rendered, but show up only with the 2nd rendered frame - // so, we forces another frame rendering the first time the imgui window is shown + // imgui windows that don't have an initial size needs to be processed once to get one + // and are not rendered in the first frame + // so, we forces to render another frame the first time the imgui window is shown + // https://github.com/ocornut/imgui/issues/2949 m_parent.set_as_dirty(); + m_parent.request_extra_frame(); m_first_input_window_render = false; } } @@ -354,23 +420,5 @@ std::string GLGizmoBase::get_name(bool include_shortcut) const return out; } - - -// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components -// were not interpolated by alpha blending or multi sampling. -unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) -{ - // 8 bit hash for the color - unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; - // Increase enthropy by a bit reversal - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - // Flip every second bit to increase the enthropy even more. - b ^= 0x55; - return b; -} - - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index c725809bba..d60e504856 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -1,10 +1,17 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoBase_hpp_ #define slic3r_GLGizmoBase_hpp_ #include "libslic3r/Point.hpp" +#include "libslic3r/Color.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/MeshUtils.hpp" +#include "slic3r/GUI/SceneRaycaster.hpp" #include @@ -26,7 +33,6 @@ class ImGuiWrapper; class GLCanvas3D; enum class CommonGizmosDataID; class CommonGizmosDataPool; -class Selection; class GLGizmoBase { @@ -34,26 +40,41 @@ public: // Starting value for ids to avoid clashing with ids used by GLVolumes // (254 is choosen to leave some space for forward compatibility) static const unsigned int BASE_ID = 255 * 255 * 254; + static const unsigned int GRABBER_ELEMENTS_MAX_COUNT = 7; static float INV_ZOOM; //BBS colors - static std::array DEFAULT_BASE_COLOR; - static std::array DEFAULT_DRAG_COLOR; - static std::array DEFAULT_HIGHLIGHT_COLOR; - static std::array, 3> AXES_COLOR; - static std::array, 3> AXES_HOVER_COLOR; - static std::array CONSTRAINED_COLOR; - static std::array FLATTEN_COLOR; - static std::array FLATTEN_HOVER_COLOR; - static std::array GRABBER_NORMAL_COL; - static std::array GRABBER_HOVER_COL; - static std::array GRABBER_UNIFORM_COL; - static std::array GRABBER_UNIFORM_HOVER_COL; + static ColorRGBA DEFAULT_BASE_COLOR; + static ColorRGBA DEFAULT_DRAG_COLOR; + static ColorRGBA DEFAULT_HIGHLIGHT_COLOR; + static std::array AXES_COLOR; + static std::array AXES_HOVER_COLOR; + static ColorRGBA CONSTRAINED_COLOR; + static ColorRGBA FLATTEN_COLOR; + static ColorRGBA FLATTEN_HOVER_COLOR; + static ColorRGBA GRABBER_NORMAL_COL; + static ColorRGBA GRABBER_HOVER_COL; + static ColorRGBA GRABBER_UNIFORM_COL; + static ColorRGBA GRABBER_UNIFORM_HOVER_COL; static void update_render_colors(); static void load_render_colors(); + enum class EGrabberExtension + { + None = 0, + PosX = 1 << 0, + NegX = 1 << 1, + PosY = 1 << 2, + NegY = 1 << 3, + PosZ = 1 << 4, + NegZ = 1 << 5, + }; + + // Represents NO key(button on keyboard) value + static const int NO_SHORTCUT_KEY_VALUE = 0; + protected: struct Grabber { @@ -63,27 +84,35 @@ protected: static const float FixedGrabberSize; static const float FixedRadiusSize; - Vec3d center; - Vec3d angles; - std::array color; - std::array hover_color; - bool enabled; - bool dragging; + bool enabled{ true }; + bool dragging{ false }; + Vec3d center{ Vec3d::Zero() }; + Vec3d angles{ Vec3d::Zero() }; + Transform3d matrix{ Transform3d::Identity() }; + ColorRGBA color{GRABBER_NORMAL_COL}; + ColorRGBA hover_color{GRABBER_HOVER_COL}; + EGrabberExtension extensions{ EGrabberExtension::None }; + // the picking id shared by all the elements + int picking_id{ -1 }; + std::array, GRABBER_ELEMENTS_MAX_COUNT> raycasters = { nullptr }; - Grabber(); + Grabber() = default; + ~Grabber(); - void render(bool hover, float size) const; - void render_for_picking(float size) const { render(size, color, true); } + void render(bool hover, float size) { render(size, hover ? hover_color : color); } float get_half_size(float size) const; float get_dragging_half_size(float size) const; - const GLModel& get_cube() const; + PickingModel &get_cube(); + + void register_raycasters_for_picking(int id); + void unregister_raycasters_for_picking(); private: - void render(float size, const std::array& render_color, bool picking) const; + void render(float size, const ColorRGBA& render_color); - GLModel cube; - bool cube_initialized = false; + static PickingModel s_cube; + static PickingModel s_cone; }; public: @@ -107,24 +136,17 @@ public: protected: GLCanvas3D& m_parent; - int m_group_id; + int m_group_id; // TODO: remove only for rotate EState m_state; int m_shortcut_key; std::string m_icon_filename; unsigned int m_sprite_id; - int m_hover_id; - bool m_dragging; - std::array m_base_color; - std::array m_drag_color; - std::array m_highlight_color; + int m_hover_id{ -1 }; + bool m_dragging{ false }; mutable std::vector m_grabbers; ImGuiWrapper* m_imgui; - bool m_first_input_window_render; - mutable std::string m_tooltip; - CommonGizmosDataPool* m_c; - GLModel m_cone; - GLModel m_cylinder; - GLModel m_sphere; + bool m_first_input_window_render{ true }; + CommonGizmosDataPool* m_c{ nullptr }; bool m_is_dark_mode = false; @@ -132,7 +154,7 @@ public: GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - virtual ~GLGizmoBase() {} + virtual ~GLGizmoBase() = default; bool init() { return on_init(); } @@ -141,9 +163,6 @@ public: std::string get_name(bool include_shortcut = true) const; - int get_group_id() const { return m_group_id; } - void set_group_id(int id) { m_group_id = id; } - EState get_state() const { return m_state; } void set_state(EState state) { m_state = state; on_set_state(); } @@ -159,7 +178,7 @@ public: virtual bool wants_enter_leave_snapshots() const { return false; } virtual std::string get_gizmo_entering_text() const { assert(false); return ""; } virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; } - virtual std::string get_action_snapshot_name() { return "Gizmo action"; } + virtual std::string get_action_snapshot_name() const; void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } virtual bool apply_clipping_plane() { return true; } @@ -168,32 +187,44 @@ public: int get_hover_id() const { return m_hover_id; } void set_hover_id(int id); - - void set_highlight_color(const std::array& color); - - void enable_grabber(unsigned int id); - void disable_grabber(unsigned int id); - - void start_dragging(); - void stop_dragging(); - + bool is_dragging() const { return m_dragging; } - void update(const UpdateData& data); - // returns True when Gizmo changed its state bool update_items_state(); - void render() { m_tooltip.clear(); on_render(); } - void render_for_picking() { on_render_for_picking(); } + void render() { on_render(); } void render_input_window(float x, float y, float bottom_limit); virtual void on_change_color_mode(bool is_dark) { m_is_dark_mode = is_dark; } + /// + /// Mouse tooltip text + /// + /// Text to be visible in mouse tooltip virtual std::string get_tooltip() const { return ""; } int get_count() { return ++count; } std::string get_gizmo_name() { return on_get_name(); } + /// + /// Is called when data (Selection) is changed + /// + virtual void data_changed(bool is_serializing){}; + + /// + /// Implement when want to process mouse events in gizmo + /// Click, Right click, move, drag, ... + /// + /// Keep information about mouse click + /// Return True when use the information and don't want to propagate it otherwise False. + virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; } + + void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); } + void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); } + + virtual bool is_in_editing_mode() const { return false; } + virtual bool is_selection_rectangle_dragging() const { return false; } + protected: float last_input_window_width = 0; virtual bool on_init() = 0; @@ -207,39 +238,51 @@ protected: virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); } virtual void on_enable_grabber(unsigned int id) {} virtual void on_disable_grabber(unsigned int id) {} + + // called inside use_grabbers virtual void on_start_dragging() {} virtual void on_stop_dragging() {} - virtual void on_update(const UpdateData& data) {} + virtual void on_dragging(const UpdateData& data) {} + virtual void on_render() = 0; - virtual void on_render_for_picking() = 0; virtual void on_render_input_window(float x, float y, float bottom_limit) {} bool GizmoImguiBegin(const std::string& name, int flags); void GizmoImguiEnd(); void GizmoImguiSetNextWIndowPos(float &x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); - // Returns the picking color for the given id, based on the BASE_ID constant - // No check is made for clashing with other picking color (i.e. GLVolumes) - std::array picking_color_component(unsigned int id) const; + + void register_grabbers_for_picking(); + void unregister_grabbers_for_picking(); + virtual void on_register_raycasters_for_picking() {} + virtual void on_unregister_raycasters_for_picking() {} + void render_grabbers(const BoundingBoxf3& box) const; void render_grabbers(float size) const; - void render_grabbers_for_picking(const BoundingBoxf3& box) const; + void render_grabbers(size_t first, size_t last, float size, bool force_hover) const; std::string format(float value, unsigned int decimals) const; // Mark gizmo as dirty to Re-Render when idle() void set_dirty(); + + /// + /// function which + /// Set up m_dragging and call functions + /// on_start_dragging / on_dragging / on_stop_dragging + /// + /// Keep information about mouse click + /// same as on_mouse + bool use_grabbers(const wxMouseEvent &mouse_event); + + void do_stop_dragging(bool perform_mouse_cleanup); + private: // Flag for dirty visible state of Gizmo // When True then need new rendering - bool m_dirty; + bool m_dirty{ false }; int count = 0; }; - -// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components -// were not interpolated by alpha blending or multi sampling. -extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index d5008725e4..c0267c9888 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1,343 +1,3838 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoCut.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include -#include -#include -#include -#include - #include #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/AppConfig.hpp" -#include "libslic3r/Model.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" +#include "imgui/imgui_internal.h" +#include "slic3r/GUI/Field.hpp" +#include "slic3r/GUI/MsgDialog.hpp" + namespace Slic3r { namespace GUI { -const double GLGizmoCut::Offset = 10.0; -const double GLGizmoCut::Margin = 20.0; -const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 }; +static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); +static const ColorRGBA UPPER_PART_COLOR = ColorRGBA::CYAN(); +static const ColorRGBA LOWER_PART_COLOR = ColorRGBA::MAGENTA(); +static const ColorRGBA MODIFIER_COLOR = ColorRGBA(0.75f, 0.75f, 0.75f, 0.5f); -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) +// connector colors +static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW(); +static const ColorRGBA DOWEL_COLOR = ColorRGBA::DARK_YELLOW(); +static const ColorRGBA HOVERED_PLAG_COLOR = ColorRGBA::CYAN(); +static const ColorRGBA HOVERED_DOWEL_COLOR = ColorRGBA(0.0f, 0.5f, 0.5f, 1.0f); +static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); +static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::DARK_GRAY(); +static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); +static const ColorRGBA CONNECTOR_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); +static const ColorRGBA HOVERED_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 1.0f); + +static const ColorRGBA CUT_PLANE_DEF_COLOR = ColorRGBA(0.9f, 0.9f, 0.9f, 0.5f); +static const ColorRGBA CUT_PLANE_ERR_COLOR = ColorRGBA(1.0f, 0.8f, 0.8f, 0.5f); + +const unsigned int AngleResolution = 64; +const unsigned int ScaleStepsCount = 72; +const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount; +const unsigned int ScaleLongEvery = 2; +const float ScaleLongTooth = 0.1f; // in percent of radius +const unsigned int SnapRegionsCount = 8; + +const float UndefFloat = -999.f; +const std::string UndefLabel = " "; + +using namespace Geometry; + +// Generates mesh for a line +static GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) +{ + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex(beg_pos); + init_data.add_vertex(end_pos); + + // indices + init_data.add_line(0, 1); + return init_data; +} + +//! -- #ysFIXME those functions bodies are ported from GizmoRotation +// Generates mesh for a circle +static void init_from_circle(GLModel& model, double radius) +{ + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(ScaleStepsCount); + init_data.reserve_indices(ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + init_data.add_vertex(Vec3f(::cos(angle) * float(radius), ::sin(angle) * float(radius), 0.0f)); + init_data.add_index(i); + } + + model.init_from(std::move(init_data)); + model.set_color(ColorRGBA::WHITE()); +} + +// Generates mesh for a scale +static void init_from_scale(GLModel& model, double radius) +{ + const float out_radius_long = float(radius) * (1.0f + ScaleLongTooth); + const float out_radius_short = float(radius) * (1.0f + 0.5f * ScaleLongTooth); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * float(radius); + const float in_y = sina * float(radius); + const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; + const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; + + // vertices + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + + // indices + init_data.add_line(i * 2, i * 2 + 1); + } + + model.init_from(std::move(init_data)); + model.set_color(ColorRGBA::WHITE()); +} + +// Generates mesh for a snap_radii +static void init_from_snap_radii(GLModel& model, double radius) +{ + const float step = 2.0f * float(PI) / float(SnapRegionsCount); + const float in_radius = float(radius) / 3.0f; + const float out_radius = 2.0f * in_radius; + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i) * step; + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * in_radius; + const float in_y = sina * in_radius; + const float out_x = cosa * out_radius; + const float out_y = sina * out_radius; + + // vertices + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + + // indices + init_data.add_line(i * 2, i * 2 + 1); + } + + model.init_from(std::move(init_data)); + model.set_color(ColorRGBA::WHITE()); +} + +// Generates mesh for a angle_arc +static void init_from_angle_arc(GLModel& model, double angle, double radius) +{ + model.reset(); + + const float step_angle = float(angle) / float(AngleResolution); + const float ex_radius = float(radius); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(1 + AngleResolution); + init_data.reserve_indices(1 + AngleResolution); + + // vertices + indices + for (unsigned int i = 0; i <= AngleResolution; ++i) { + const float angle = float(i) * step_angle; + init_data.add_vertex(Vec3f(::cos(angle) * ex_radius, ::sin(angle) * ex_radius, 0.0f)); + init_data.add_index(i); + } + + model.init_from(std::move(init_data)); +} + +//! -- + +GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -{} - -std::string GLGizmoCut::get_tooltip() const + , m_connectors_group_id (GrabberID::Count) + , m_connector_type (CutConnectorType::Plug) + , m_connector_style (int(CutConnectorStyle::Prism)) + , m_connector_shape_id (int(CutConnectorShape::Circle)) { - double cut_z = m_cut_z; - if (wxGetApp().app_config->get("use_inches") == "1") - cut_z *= ObjectManipulation::mm_to_in; + m_modes = { _u8L("Planar"), _u8L("Dovetail")//, _u8L("Grid") +// , _u8L("Radial"), _u8L("Modular") + }; - return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : ""; + m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; + + m_connector_types = { _u8L("Plug"), _u8L("Dowel"), _u8L("Snap") }; + + m_connector_styles = { _u8L("Prism"), _u8L("Frustum") +// , _u8L("Claw") + }; + + m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Hexagon"), _u8L("Circle") +// , _u8L("D-shape") + }; + + m_axis_names = { "X", "Y", "Z" }; + + m_part_orientation_names = { + {"none", _L("Keep orientation")}, + {"on_cut", _L("Place on cut")}, + {"flip", _L("Flip upside down")}, + }; + + m_labels_map = { + {"Connectors" , _u8L("Connectors")}, + {"Type" , _u8L("Type")}, + {"Style" , _u8L("Style")}, + {"Shape" , _u8L("Shape")}, + {"Depth" , _u8L("Depth")}, + {"Size" , _u8L("Size")}, + {"Rotation" , _u8L("Rotation")}, + {"Groove" , _u8L("Groove")}, + {"Width" , _u8L("Width")}, + {"Flap Angle" , _u8L("Flap Angle")}, + {"Groove Angle" , _u8L("Groove Angle")}, + }; + +// update_connector_shape(); } -bool GLGizmoCut::on_init() +std::string GLGizmoCut3D::get_tooltip() const { - m_grabbers.emplace_back(); - m_shortcut_key = WXK_CONTROL_C; - return true; + std::string tooltip; + if (m_hover_id == Z || (m_dragging && m_hover_id == CutPlane)) { + double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0; + std::string unit_str = " " + (m_imperial_units ? _u8L("in") : _u8L("mm")); + const BoundingBoxf3& tbb = m_transformed_bounding_box; + + const std::string name = m_keep_as_parts ? _u8L("Part") : _u8L("Object"); + if (tbb.max.z() >= 0.0) { + double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; + tooltip += format(static_cast(top), 2) + " " + unit_str + " (" + name + " A)"; + if (tbb.min.z() <= 0.0) + tooltip += "\n"; + } + if (tbb.min.z() <= 0.0) { + double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; + tooltip += format(static_cast(bottom), 2) + " " + unit_str + " (" + name + " B)"; + } + return tooltip; + } + + if (!m_dragging && m_hover_id == CutPlane) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return _u8L("Click to flip the cut plane\n" + "Drag to move the cut plane"); + return _u8L("Click to flip the cut plane\n" + "Drag to move the cut plane\n" + "Right-click a part to assign it to the other side"); + } + + if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation)) { + std::string axis = m_hover_id == X ? "X" : m_hover_id == Y ? "Y" : "Z"; + return axis + ": " + format(float(rad2deg(m_angle)), 1) + _u8L("°"); + } + + return tooltip; } -std::string GLGizmoCut::on_get_name() const +bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) { - return _u8L("Cut"); + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + if (mouse_event.ShiftDown() && mouse_event.LeftDown()) + return gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + if (mouse_event.CmdDown() && mouse_event.LeftDown()) + return false; + if (cut_line_processing()) { + if (mouse_event.ShiftDown()) { + if (mouse_event.Moving()|| mouse_event.Dragging()) + return gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + if (mouse_event.LeftUp()) + return gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + } + discard_cut_line_processing(); + } + else if (mouse_event.Moving()) + return false; + + if (m_hover_id >= CutPlane && mouse_event.LeftDown() && !m_connectors_editing) { + // before processing of a use_grabbers(), detect start move position as a projection of mouse position to the cut plane + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false)) + m_cut_plane_start_move_pos = pos_world; + } + + if (use_grabbers(mouse_event)) { + if (m_hover_id >= m_connectors_group_id) { + if (mouse_event.LeftDown() && !mouse_event.CmdDown() && !mouse_event.AltDown()) + unselect_all_connectors(); + if (mouse_event.LeftUp() && !mouse_event.ShiftDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + } + else if (m_hover_id == CutPlane) { + if (mouse_event.LeftDown()) { + m_was_cut_plane_dragged = m_was_contour_selected = false; + + // disable / enable current contour + Vec3d pos; + Vec3d pos_world; + m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world); + if (m_was_contour_selected) { + // Following would inform the clipper about the mouse click, so it can + // toggle the respective contour as disabled. + //m_c->object_clipper()->pass_mouse_click(pos_world); + //process_contours(); + return true; + } + + } + else if (mouse_event.LeftUp() && !m_was_cut_plane_dragged && !m_was_contour_selected) + flip_cut_plane(); + } + + if (m_hover_id >= CutPlane && mouse_event.Dragging() && !m_connectors_editing) { + // if we continue to dragging a cut plane, than update a start move position as a projection of mouse position to the cut plane after processing of a use_grabbers() + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false)) + m_cut_plane_start_move_pos = pos_world; + } + + toggle_model_objects_visibility(); + return true; + } + + static bool pending_right_up = false; + if (mouse_event.LeftDown()) { + bool grabber_contains_mouse = (get_hover_id() != -1); + const bool shift_down = mouse_event.ShiftDown(); + if ((!shift_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + return true; + } + else if (mouse_event.Dragging()) { + bool control_down = mouse_event.CmdDown(); + if (m_parent.get_move_volume_id() != -1) { + // don't allow dragging objects with the Sla gizmo on + return true; + } + if (!control_down && + gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } + if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) { + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) + pending_right_up = false; + } + } + else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) { + // in case SLA/FDM gizmo is selected, we just pass the LeftUp event + // and stop processing - neither object moving or selecting is + // suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + return true; + } + else if (mouse_event.RightDown()) { + if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE && + CutMode(m_mode) == CutMode::cutPlanar) { + // Check the internal part raycasters. + if (! m_part_selection.valid()) + process_contours(); + m_part_selection.toggle_selection(mouse_pos); + check_and_update_connectors_state(); // after a contour is deactivated, its connectors are inside the object + return true; + } + + if (m_parent.get_selection().get_object_idx() != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { + // we need to set the following right up as processed to avoid showing + // the context menu if the user release the mouse over the object + pending_right_up = true; + // event was taken care of by the SlaSupports gizmo + return true; + } + } + else if (pending_right_up && mouse_event.RightUp()) { + pending_right_up = false; + return true; + } + return false; } -void GLGizmoCut::on_set_state() +void GLGizmoCut3D::shift_cut(double delta) { - // Reset m_cut_z on gizmo activation - if (get_state() == On) - m_cut_z = bounding_box().center().z(); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); + set_center(m_plane_center + m_cut_normal * delta, true); + m_ar_plane_center = m_plane_center; } -bool GLGizmoCut::on_is_activable() const +void GLGizmoCut3D::rotate_vec3d_around_plane_center(Vec3d&vec) { - const Selection& selection = m_parent.get_selection(); - return selection.is_single_full_instance() && !selection.is_wipe_tower(); + vec = Transformation(translation_transform(m_plane_center) * m_rotation_m * translation_transform(-m_plane_center)).get_matrix() * vec; } -void GLGizmoCut::on_start_dragging() +void GLGizmoCut3D::put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset) { - if (m_hover_id == -1) + ModelObject* mo = m_c->selection_info()->model_object(); + if (CutConnectors& connectors = mo->cut_connectors; !connectors.empty()) { + const float sla_shift = m_c->selection_info()->get_sla_shift(); + const Vec3d& instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); + + for (auto& connector : connectors) { + // convert connetor pos to the world coordinates + Vec3d pos = connector.pos + instance_offset; + pos[Z] += sla_shift; + // scalar distance from point to plane along the normal + double distance = -cp_normal.dot(pos) + cp_offset; + // move connector + connector.pos += distance * cp_normal; + } + } +} + +// returns true if the camera (forward) is pointing in the negative direction of the cut normal +bool GLGizmoCut3D::is_looking_forward() const +{ + const Camera& camera = wxGetApp().plater()->get_camera(); + const double dot = camera.get_dir_forward().dot(m_cut_normal); + return dot < 0.05; +} + +void GLGizmoCut3D::update_clipper() +{ + // update cut_normal + Vec3d normal = m_rotation_m * Vec3d::UnitZ(); + normal.normalize(); + m_cut_normal = normal; + + // calculate normal and offset for clipping plane + Vec3d beg = m_bb_center; + beg[Z] -= m_radius; + rotate_vec3d_around_plane_center(beg); + + m_clp_normal = normal; + double offset = normal.dot(m_plane_center); + double dist = normal.dot(beg); + + m_parent.set_color_clip_plane(normal, offset); + + if (!is_looking_forward()) { + // recalculate normal and offset for clipping plane, if camera is looking downward to cut plane + normal = m_rotation_m * (-1. * Vec3d::UnitZ()); + normal.normalize(); + + beg = m_bb_center; + beg[Z] += m_radius; + rotate_vec3d_around_plane_center(beg); + + m_clp_normal = normal; + offset = normal.dot(m_plane_center); + dist = normal.dot(beg); + } + + m_c->object_clipper()->set_range_and_pos(normal, offset, dist); + + put_connectors_on_cut_plane(normal, offset); + + if (m_raycasters.empty()) + on_register_raycasters_for_picking(); + else + update_raycasters_for_picking_transform(); +} + +void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/) +{ + set_center_pos(center, update_tbb); + check_and_update_connectors_state(); + update_clipper(); +} + +void GLGizmoCut3D::switch_to_mode(size_t new_mode) +{ + m_mode = new_mode; + update_raycasters_for_picking(); + + apply_color_clip_plane_colors(); + if (auto oc = m_c->object_clipper()) { + m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; + oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); + } + + update_plane_model(); + reset_cut_by_contours(); +} + +bool GLGizmoCut3D::render_cut_mode_combo() +{ + ImGui::AlignTextToFramePadding(); + ImGuiWrapper::push_combo_style(m_parent.get_scale()); + int selection_idx = int(m_mode); + const bool is_changed = m_imgui->combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width); + ImGuiWrapper::pop_combo_style(); + + if (is_changed) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Change cut mode"), UndoRedo::SnapshotType::GizmoAction); + switch_to_mode(size_t(selection_idx)); + check_and_update_connectors_state(); + } + + return is_changed; +} + +bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, int& selection_idx) +{ + ImGuiWrapper::push_combo_style(m_parent.get_scale()); + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_editing_window_width); + + size_t selection_out = selection_idx; + + const char* selected_str = (selection_idx >= 0 && selection_idx < int(lines.size())) ? lines[selection_idx].c_str() : ""; + if (ImGui::BBLBeginCombo(("##" + label).c_str(), selected_str, 0)) { + for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { + ImGui::PushID(int(line_idx)); + if (ImGui::Selectable("", line_idx == selection_idx)) + selection_out = line_idx; + + ImGui::SameLine(); + ImGui::Text("%s", lines[line_idx].c_str()); + ImGui::PopID(); + } + + ImGui::EndCombo(); + } + + bool is_changed = selection_idx != selection_out; + selection_idx = selection_out; + + //if (is_changed) update_connector_shape(); + ImGuiWrapper::pop_combo_style(); + + return is_changed; +} + +bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_in) +{ + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width); + + double value = value_in; + if (m_imperial_units) + value *= GizmoObjectManipulation::mm_to_in; + double old_val = value; + ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + + ImGui::SameLine(); + m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + + value_in = value * (m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0); + return !is_approx(old_val, value); +} + +bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) +{ + // -------- [ ] -------- [ ] + // slider_with + item_in_gap + first_input_width + item_out_gap + slider_with + item_in_gap + second_input_width + double slider_with = 0.24 * m_editing_window_width; // m_control_width * 0.35; + double item_in_gap = 0.01 * m_editing_window_width; + double item_out_gap = 0.01 * m_editing_window_width; + double first_input_width = 0.29 * m_editing_window_width; + double second_input_width = 0.29 * m_editing_window_width; + + constexpr float UndefMinVal = -0.1f; + const float f_mm_to_in = static_cast(GizmoObjectManipulation::mm_to_in); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(slider_with); + + double left_width = m_label_width + slider_with + item_in_gap; + + bool m_imperial_units = false; + + float value = value_in; + if (m_imperial_units) + value *= f_mm_to_in; + float old_val = value; + + const BoundingBoxf3 bbox = m_bounding_box; + const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f); + const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/min_val : 1.f; + + float min_size = value_in < 0.f ? UndefMinVal : min_v; + if (m_imperial_units) { + min_size *= f_mm_to_in; + } + std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); + + m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, mean_size, format.c_str()); + + ImGui::SameLine(left_width); + ImGui::PushItemWidth(first_input_width); + ImGui::BBLDragFloat(("##input_" + label).c_str(), &value, 0.05f, min_size, mean_size, format.c_str()); + + value_in = value * float(m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0); + + left_width += (first_input_width + item_out_gap); + ImGui::SameLine(left_width); + ImGui::PushItemWidth(slider_with); + + float tolerance = tolerance_in; + if (m_imperial_units) + tolerance *= f_mm_to_in; + float old_tolerance = tolerance; + // std::string format_t = tolerance_in < 0.f ? " " : "%.f %%"; + float min_tolerance = tolerance_in < 0.f ? UndefMinVal : 0.f; + const float max_tolerance_v = max_tolerance > 0.f ? std::min(max_tolerance, 0.5f * mean_size) : 0.5f * mean_size; + + m_imgui->bbl_slider_float_style(("##tolerance_" + label).c_str(), &tolerance, min_tolerance, max_tolerance_v, format.c_str(), 1.f, true, + _L("Tolerance")); + + left_width += (slider_with + item_in_gap); + ImGui::SameLine(left_width); + ImGui::PushItemWidth(second_input_width); + ImGui::BBLDragFloat(("##tolerance_input_" + label).c_str(), &tolerance, 0.05f, min_tolerance, max_tolerance_v, format.c_str()); + + tolerance_in = tolerance * float(m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0); + + return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance); +} + +void GLGizmoCut3D::render_move_center_input(int axis) +{ + m_imgui->text(m_axis_names[axis]+":"); + ImGui::SameLine(); + ImGui::PushItemWidth(0.3f*m_control_width); + + Vec3d move = m_plane_center; + double in_val, value = in_val = move[axis]; + if (m_imperial_units) + value *= GizmoObjectManipulation::mm_to_in; + ImGui::InputDouble(("##move_" + m_axis_names[axis]).c_str(), &value, 0.0, 0.0, "%.2f", ImGuiInputTextFlags_CharsDecimal); + ImGui::SameLine(); + + double val = value * (m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0); + + if (in_val != val) { + move[axis] = val; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); + set_center(move, true); + m_ar_plane_center = m_plane_center; + + reset_cut_by_contours(); + } +} + +bool GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type) +{ + ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 0); + ImGui::PushItemWidth(m_control_width); + if (ImGui::RadioButton(m_connector_types[size_t(type)].c_str(), m_connector_type == type)) { + m_connector_type = type; +// update_connector_shape(); + return true; + } + return false; +} + +void GLGizmoCut3D::render_connect_mode_radio_button(CutConnectorMode mode) +{ + ImGui::SameLine(mode == CutConnectorMode::Auto ? m_label_width : 2 * m_label_width); + ImGui::PushItemWidth(m_control_width); + if (ImGui::RadioButton(m_connector_modes[int(mode)].c_str(), m_connector_mode == mode)) + m_connector_mode = mode; +} + +bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::string& tooltip) const +{ + const ImGuiStyle &style = ImGui::GetStyle(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {1, style.ItemSpacing.y}); + + ImGui::PushStyleColor(ImGuiCol_Button, {0.25f, 0.25f, 0.25f, 0.0f}); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, {0.4f, 0.4f, 0.4f, 1.0f}); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, {0.4f, 0.4f, 0.4f, 1.0f}); + + const bool revert = m_imgui->button(wxString(ImGui::RevertBtn) + "##" + label_id); + + ImGui::PopStyleColor(3); + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(tooltip.c_str(), ImGui::GetFontSize() * 20.0f); + + ImGui::PopStyleVar(); + + return revert; +} + +static double get_grabber_mean_size(const BoundingBoxf3& bb) +{ + return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.; +} + +indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() +{ + // values for calculation + + const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); + const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle); + + const float groove_half_width_upper = 0.5f * (m_groove.width); + const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width); + + const float cut_plane_radius = 1.5f * float(m_radius); + const float cut_plane_length = 1.5f * cut_plane_radius; + + const float groove_half_depth = 0.5f * m_groove.depth; + + const float x = 0.5f * cut_plane_radius; + const float y = 0.5f * cut_plane_length; + float z_upper = groove_half_depth; + float z_lower = -groove_half_depth; + + const float proj = y * tan(m_groove.angle); + + float ext_upper_x = groove_half_width_upper + proj; // upper_x extension + float ext_lower_x = groove_half_width_lower + proj; // lower_x extension + + float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing + float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing + + const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes + + // Vertices of the groove used to detection if groove is valid + // They are written as: + // {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper, + // right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper } + { + m_groove_vertices.clear(); + m_groove_vertices.reserve(8); + + m_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f( ext_lower_x, -y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f( nar_lower_x, y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f( ext_upper_x, -y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f( nar_upper_x, y, z_upper).cast()); + } + + // Different cases of groove plane: + + // groove is open + + if (groove_half_width_upper > proj && groove_half_width_lower > proj) { + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) { + return std::vector({ + // upper left part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {-nar_upper_x, y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}, + // upper right part vertices + {ext_upper_x, -y, z_upper}, {nar_upper_x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper} + }); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + + nar_upper_x += under_x_shift; + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {5,4,7}, {5,7,6}, // lower part + {3,4,5}, {3,5,2}, // left side + {9,6,8}, {8,6,7}, // right side + {1,0,2}, {2,0,3}, // upper left part + {9,8,10}, {10,8,11}, // upper right part + // under view + {20,21,22}, {20,22,23}, // upper right part + {12,13,14}, {12,14,15}, // upper left part + {18,21,20}, {18,20,19}, // right side + {16,15,14}, {16,14,17}, // left side + {16,17,18}, {16,18,19}, // lower part + // left edge + {1,13,0}, {0,13,12}, + // front edge + {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, + // right edge + {11,23,10}, {10,23,22}, + // back edge + {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} + }; + return mesh; + } + + float cross_pt_upper_y = groove_half_width_upper / tan(m_groove.angle); + + // groove is closed + + if (groove_half_width_upper < proj && groove_half_width_lower < proj) { + float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle); + + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) { + return std::vector({ + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {ext_lower_x, -y, z_lower} + }); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + + cross_pt_upper_y += cut_plane_thiknes; + cross_pt_lower_y += cut_plane_thiknes; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {8,7,9}, // lower part + {5,8,6}, {6,8,7}, // left side + {4,9,8}, {4,8,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part + {18,15,14}, {14,18,19}, // right side + {17,16,15}, {17,15,18}, // left side + {17,18,19}, // lower part + // left edge + {1,11,0}, {0,11,10}, + // front edge + {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, + // right edge + {3,13,12}, {3,12,2}, + // back edge + {2,12,11}, {2,11,1} + }; + + return mesh; + } + + // groove is closed from the roof + + indexed_triangle_set mesh; + mesh.vertices = { + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} + }; + + mesh.vertices.reserve(2 * mesh.vertices.size() + 1); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = { + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {under_x_shift, cross_pt_upper_y, z_upper}, {-under_x_shift, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} + }; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {8,7,10}, {8,10,9}, // lower part + {5,8,7}, {5,7,6}, // left side + {4,10,9}, {4,9,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part + {21,16,15}, {21,15,22}, // right side + {19,18,17}, {19,17,20}, // left side + {19,20,21}, {19,21,22}, // lower part + // left edge + {1,12,11}, {1,11,0}, + // front edge + {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, + // right edge + {3,14,13}, {3,14,2}, + // back edge + {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} + }; + + return mesh; +} + +void GLGizmoCut3D::render_cut_plane() +{ + if (cut_line_processing()) return; - const BoundingBoxf3 box = bounding_box(); - m_max_z = box.max.z(); - m_start_z = m_cut_z; - m_drag_pos = m_grabbers[m_hover_id].center; - m_drag_center = box.center(); - m_drag_center.z() = m_cut_z; -} + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; -void GLGizmoCut::on_update(const UpdateData& data) -{ - if (m_hover_id != -1) - set_cut_z(m_start_z + calc_projection(data.mouse_ray)); -} - -void GLGizmoCut::on_render() -{ - const BoundingBoxf3 box = bounding_box(); - Vec3d plane_center = box.center(); - plane_center.z() = m_cut_z; - m_max_z = box.max.z(); - set_cut_z(m_cut_z); - - update_contours(); - - const float min_x = box.min.x() - Margin; - const float max_x = box.max.x() + Margin; - const float min_y = box.min.y() - Margin; - const float max_y = box.max.y() + Margin; glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - // Draw the cutting plane - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, plane_center.z()); - ::glVertex3f(max_x, min_y, plane_center.z()); - ::glVertex3f(max_x, max_y, plane_center.z()); - ::glVertex3f(min_x, max_y, plane_center.z()); - glsafe(::glEnd()); + shader->start_using(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + ColorRGBA cp_clr = can_perform_cut() && has_valid_groove() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR; + if (m_mode == size_t(CutMode::cutTongueAndGroove)) + cp_clr.a(cp_clr.a() - 0.1f); + m_plane.model.set_color(cp_clr); + + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; + shader->set_uniform("view_model_matrix", view_model_matrix); + m_plane.model.render(); glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); - // TODO: draw cut part contour? + shader->stop_using(); +} - // Draw the grabber and the connecting line - m_grabbers[0].center = plane_center; - m_grabbers[0].center.z() = plane_center.z() + Offset; +static double get_half_size(double size) +{ + return std::max(size * 0.35, 0.05); +} +static double get_dragging_half_size(double size) +{ + return get_half_size(size) * 1.25; +} + +void GLGizmoCut3D::render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix) +{ + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader) { + shader->start_using(); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("emission_factor", 0.2f); + shader->set_uniform("projection_matrix", wxGetApp().plater()->get_camera().get_projection_matrix()); + + model.set_color(color); + model.render(); + + shader->stop_using(); + } +} + +void GLGizmoCut3D::render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width) +{ + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader) { + shader->start_using(); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", wxGetApp().plater()->get_camera().get_projection_matrix()); + shader->set_uniform("width", width); + + line_model.set_color(color); + line_model.render(); + + shader->stop_using(); + } +} + +void GLGizmoCut3D::render_rotation_snapping(GrabberID axis, const ColorRGBA& color) +{ + GLShaderProgram* line_shader = wxGetApp().get_shader("flat"); + if (!line_shader) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_start_dragging_m; + + if (axis == X) + view_model_matrix = view_model_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ()); + else if (axis == Y) + view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * rotation_transform(-0.5 * PI * Vec3d::UnitY()); + else + view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()); + + line_shader->start_using(); + line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + line_shader->set_uniform("view_model_matrix", view_model_matrix); + line_shader->set_uniform("width", 0.25f); + + m_circle.render(); + m_scale.render(); + m_snap_radii.render(); + m_reference_radius.render(); + if (m_dragging) { + line_shader->set_uniform("width", 1.5f); + m_angle_arc.set_color(color); + m_angle_arc.render(); + } + + line_shader->stop_using(); +} + +void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef/* = 1.0*/) +{ + const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, line_len_koef * m_grabber_connection_len)); + + render_line(m_grabber_connection, color, line_view_matrix, 0.2f); +}; + +void GLGizmoCut3D::render_cut_plane_grabbers() +{ glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); - glsafe(::glColor3f(1.0, 1.0, 0.0)); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center.data()); - ::glVertex3dv(m_grabbers[0].center.data()); - glsafe(::glEnd()); + ColorRGBA color = ColorRGBA::GRAY(); - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); + const Transform3d view_matrix = wxGetApp().plater()->get_camera().get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; - m_grabbers[0].color = GrabberColor; - m_grabbers[0].render(m_hover_id == 0, (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0)); + const double mean_size = get_grabber_mean_size(m_bounding_box); + double size; - shader->stop_using(); + const bool no_xy_dragging = m_dragging && m_hover_id == CutPlane; - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); - glsafe(::glLineWidth(2.0f)); - m_cut_contours.contours.render(); - glsafe(::glPopMatrix()); -} + if (!no_xy_dragging && m_hover_id != CutPlaneZRotation && m_hover_id != CutPlaneXMove && m_hover_id != CutPlaneYMove) { + render_grabber_connection(GRABBER_COLOR, view_matrix); -void GLGizmoCut::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); -} - -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) -{ - //static float last_y = 0.0f; - //static float last_h = 0.0f; - - //BBS: GUI refactor: move gizmo to the right -#if BBS_TOOLBAR_ON_TOP - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.0f); -#else - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f); -#endif - - //BBS - ImGuiWrapper::push_toolbar_style(); - - m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - - // adjust window position to avoid overlap the view toolbar - /*const float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if (last_h != win_h || last_y != y) { - // ask canvas for another frame to render the window in the correct position -#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT - m_imgui->set_requires_extra_frame(); -#else - m_parent.request_extra_frame(); -#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; - }*/ - - ImGui::AlignTextToFramePadding(); - m_imgui->text("Z"); - ImGui::SameLine(); - ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); - - double cut_z = m_cut_z; - if (imperial_units) - cut_z *= ObjectManipulation::mm_to_in; - ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); - - ImGui::SameLine(); - m_imgui->text(imperial_units ? _L("in") : _L("mm")); - - m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); - - ImGui::Separator(); - - m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); - m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); - m_imgui->checkbox(_L("Cut to parts"), m_cut_to_parts); // BBS - m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); - - // BBS - ImGui::Separator(); - m_imgui->checkbox(_L("Auto Segment"), m_do_segment); - m_imgui->disabled_begin(!m_do_segment); - ImGui::InputDouble("smoothing_alpha", &m_segment_smoothing_alpha, 0.0f, 0.0f, "%.2f"); - m_segment_smoothing_alpha = std::max(0.1, std::min(100.0, m_segment_smoothing_alpha)); - ImGui::InputInt("segment number", &m_segment_number); - m_segment_number = std::max(1, m_segment_number); - m_imgui->disabled_end(); - - ImGui::Separator(); - - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower && !m_do_segment) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); - const bool cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); - - m_imgui->end(); - - //BBS - ImGuiWrapper::pop_toolbar_style(); - - // BBS: m_do_segment - if (cut_clicked && (m_keep_upper || m_keep_lower || m_do_segment)) - perform_cut(m_parent.get_selection()); -} - -void GLGizmoCut::set_cut_z(double cut_z) -{ - // Clamp the plane to the object's bounding box - m_cut_z = std::clamp(cut_z, 0.0, m_max_z); -} - -void GLGizmoCut::perform_cut(const Selection& selection) -{ - const int instance_idx = selection.get_instance_idx(); - const int object_idx = selection.get_object_idx(); - - wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - - // m_cut_z is the distance from the bed. Subtract possible SLA elevation. - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); - - // BBS: do segment - if (m_do_segment) - { - wxGetApp().plater()->segment(object_idx, instance_idx, m_segment_smoothing_alpha, m_segment_number); + // render sphere grabber + size = m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size); + color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : + m_hover_id == X ? complementary(ColorRGBA::RED()) : + m_hover_id == Z ? GRABBER_COLOR : ColorRGBA::GRAY(); + render_model(m_sphere.model, color, view_matrix * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size)); } - else if (0.0 < object_cut_z && object_cut_z < m_max_z) { - //wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower, m_cut_to_parts); + + const bool no_xy_grabber_hovered = !m_dragging && (m_hover_id < 0 || m_hover_id == CutPlane); + + // render X grabber + + if (no_xy_grabber_hovered || m_hover_id == X) + { + size = m_dragging && m_hover_id == X ? get_dragging_half_size(mean_size) : get_half_size(mean_size); + const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); + + if (m_hover_id == X) { + render_grabber_connection(color, view_matrix); + render_rotation_snapping(X, color); + } + + Vec3d offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + } + + // render Y grabber + + if (no_xy_grabber_hovered || m_hover_id == Y) + { + size = m_dragging && m_hover_id == Y ? get_dragging_half_size(mean_size) : get_half_size(mean_size); + const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); + + if (m_hover_id == Y) { + render_grabber_connection(color, view_matrix); + render_rotation_snapping(Y, color); + } + + Vec3d offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + + // render CutPlaneZRotation grabber + + if (no_xy_grabber_hovered || m_hover_id == CutPlaneZRotation) + { + size = 0.75 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = ColorRGBA::BLUE(); + const ColorRGBA cp_color = m_hover_id == CutPlaneZRotation ? color : m_plane.model.get_color(); + + const double grabber_shift = -1.75 * m_grabber_connection_len; + + render_model(m_sphere.model, cp_color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + + if (m_hover_id == CutPlaneZRotation) { + const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + + render_rotation_snapping(CutPlaneZRotation, color); + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitX()), 1.75); + + Vec3d offset = Vec3d(1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + offset = Vec3d(-1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + } + + const double xy_connection_len = 0.75 * m_grabber_connection_len; + + // render CutPlaneXMove grabber + + if (no_xy_grabber_hovered || m_hover_id == CutPlaneXMove) + { + size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = m_hover_id == CutPlaneXMove ? ColorRGBA::RED() : m_plane.model.get_color(); + + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()), 0.75); + + Vec3d offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones(); + render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size)); + + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = (size + xy_connection_len) * Vec3d::UnitX(); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + + // render CutPlaneYMove grabber + + if (m_groove.angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove)) + { + size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = m_hover_id == CutPlaneYMove ? ColorRGBA::GREEN() : m_plane.model.get_color(); + + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitX()), 0.75); + + Vec3d offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones(); + render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size)); + + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = (size + xy_connection_len) * Vec3d::UnitY(); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + } + } +} + +void GLGizmoCut3D::render_cut_line() +{ + if (!cut_line_processing() || m_line_end.isApprox(Vec3d::Zero())) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + m_cut_line.reset(); + m_cut_line.init_from(its_make_line((Vec3f)m_line_beg.cast(), (Vec3f)m_line_end.cast())); + + render_line(m_cut_line, GRABBER_COLOR, wxGetApp().plater()->get_camera().get_view_matrix(), 0.25f); +} + +bool GLGizmoCut3D::on_init() +{ + m_grabbers.emplace_back(); + m_shortcut_key = WXK_CONTROL_C; + + // initiate info shortcuts + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString shift = "Shift+"; + + m_shortcuts_cut.push_back(std::make_pair(shift + _L("Drag"), _L("Draw cut line"))); + + m_shortcuts_connector.push_back(std::make_pair(_L("Left click"), _L("Add connector"))); + m_shortcuts_connector.push_back(std::make_pair(_L("Right click"), _L("Remove connector"))); + m_shortcuts_connector.push_back(std::make_pair(_L("Drag"), _L("Move connector"))); + m_shortcuts_connector.push_back(std::make_pair(shift + _L("Left click"), _L("Add connector to selection"))); + m_shortcuts_connector.push_back(std::make_pair(alt + _L("Left click"), _L("Remove connector from selection"))); + m_shortcuts_connector.push_back(std::make_pair(ctrl + "A", _L("Select all connectors"))); + + return true; +} + +void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) +{ + size_t mode; + float groove_depth; + float groove_width; + float groove_flaps_angle; + float groove_angle; + float groove_depth_tolerance; + float groove_width_tolerance; + + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, mode, m_connectors_editing, + m_ar_plane_center, m_rotation_m, + groove_depth, groove_width, groove_flaps_angle, groove_angle, groove_depth_tolerance, groove_width_tolerance); + + m_start_dragging_m = m_rotation_m; + + m_transformed_bounding_box = transformed_bounding_box(m_ar_plane_center, m_rotation_m); + set_center_pos(m_ar_plane_center); + + if (m_mode != mode) + switch_to_mode(mode); + else if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (!is_approx(m_groove.depth , groove_depth) || + !is_approx(m_groove.width , groove_width) || + !is_approx(m_groove.flaps_angle , groove_flaps_angle) || + !is_approx(m_groove.angle , groove_angle) || + !is_approx(m_groove.depth_tolerance, groove_depth_tolerance) || + !is_approx(m_groove.width_tolerance, groove_width_tolerance) ) + { + m_groove.depth = groove_depth; + m_groove.width = groove_width; + m_groove.flaps_angle = groove_flaps_angle; + m_groove.angle = groove_angle; + m_groove.depth_tolerance= groove_depth_tolerance; + m_groove.width_tolerance= groove_width_tolerance; + update_plane_model(); + } + reset_cut_by_contours(); + } + + m_parent.request_extra_frame(); +} + +void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing, + m_ar_plane_center, m_start_dragging_m, + m_groove.depth, m_groove.width, m_groove.flaps_angle, m_groove.angle, m_groove.depth_tolerance, m_groove.width_tolerance); +} + +std::string GLGizmoCut3D::on_get_name() const +{ + return _u8L("Cut"); +} + +void GLGizmoCut3D::apply_color_clip_plane_colors() +{ + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + m_parent.set_color_clip_plane_colors({ CUT_PLANE_DEF_COLOR , CUT_PLANE_DEF_COLOR }); + else + m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR }); +} + +void GLGizmoCut3D::on_set_state() +{ + if (m_state == On) { + m_parent.set_use_color_clip_plane(true); + + update_bb(); + m_connectors_editing = !m_selected.empty(); + m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m); + + // initiate archived values + m_ar_plane_center = m_plane_center; + m_start_dragging_m = m_rotation_m; + reset_cut_by_contours(); + + m_parent.request_extra_frame(); } else { - // the object is SLA-elevated and the plane is under it. + if (auto oc = m_c->object_clipper()) { + oc->set_behavior(true, true, 0.); + oc->release(); + } + m_selected.clear(); + m_parent.set_use_color_clip_plane(false); + //m_c->selection_info()->set_use_shift(false); + + // Make sure that the part selection data are released when the gizmo is closed. + // The CallAfter is needed because in perform_cut, the gizmo is closed BEFORE + // the cut is performed (because of undo/redo snapshots), so the data would + // be deleted prematurely. + if (m_part_selection.valid()) + wxGetApp().CallAfter([this]() { m_part_selection = PartSelection(); }); } } -double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const +void GLGizmoCut3D::on_register_raycasters_for_picking() { - double projection = 0.0; + // assert(m_raycasters.empty()); + if (!m_raycasters.empty()) + on_unregister_raycasters_for_picking(); + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); - const Vec3d starting_vec = m_drag_pos - m_drag_center; - const double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) { - const Vec3d mouse_dir = mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + init_picking_models(); + + if (m_connectors_editing) { + if (CommonGizmosDataObjects::SelectionInfo* si = m_c->selection_info()) { + const CutConnectors& connectors = si->model_object()->cut_connectors; + for (int i = 0; i < int(connectors.size()); ++i) + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i + m_connectors_group_id, *(m_shapes[connectors[i].attribs]).mesh_raycaster, Transform3d::Identity())); + } + } + else if (!cut_line_processing()) { + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Y, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Y, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_sphere.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::FallbackGizmo, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity())); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_sphere.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cube.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cube.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + } + } + + update_raycasters_for_picking_transform(); +} + +void GLGizmoCut3D::on_unregister_raycasters_for_picking() +{ + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::FallbackGizmo); + m_raycasters.clear(); + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(false); +} + +void GLGizmoCut3D::update_raycasters_for_picking() +{ + on_unregister_raycasters_for_picking(); + on_register_raycasters_for_picking(); +} + +void GLGizmoCut3D::set_volumes_picking_state(bool state) +{ + std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + if (raycasters != nullptr) { + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList ids = selection.get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume* v = selection.get_volume(id); + auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr item) { return item->get_raycaster() == v->mesh_raycaster.get(); }); + if (it != raycasters->end()) + (*it)->set_active(state); + } + } +} + +void GLGizmoCut3D::update_raycasters_for_picking_transform() +{ + if (m_connectors_editing) { + CommonGizmosDataObjects::SelectionInfo* si = m_c->selection_info(); + if (!si) + return; + const ModelObject* mo = si->model_object(); + const CutConnectors& connectors = mo->cut_connectors; + if (connectors.empty()) + return; + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + + const Vec3d& instance_offset = mo->instances[inst_id]->get_offset(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + + const bool looking_forward = is_looking_forward(); + + for (size_t i = 0; i < connectors.size(); ++i) { + const CutConnector& connector = connectors[i]; + + float height = connector.height; + // recalculate connector position to world position + Vec3d pos = connector.pos + instance_offset; + if (connector.attribs.type == CutConnectorType::Dowel && + connector.attribs.style == CutConnectorStyle::Prism) { + height = 0.05f; + if (!looking_forward) + pos += 0.05 * m_clp_normal; + } + pos[Z] += sla_shift; + + const Transform3d scale_trafo = scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); + m_raycasters[i]->set_transform(translation_transform(pos) * m_rotation_m * scale_trafo); + } + } + else if (!cut_line_processing()){ + const Transform3d trafo = translation_transform(m_plane_center) * m_rotation_m; + + const BoundingBoxf3 box = m_bounding_box; + + const double size = get_half_size(get_grabber_mean_size(box)); + Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + + int id = 0; + + Vec3d offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(scale)); + offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitX()) * scale_transform(scale)); + + offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + + m_raycasters[id++]->set_transform(trafo * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size)); + + m_raycasters[id++]->set_transform(trafo); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + + double grabber_y_shift = -1.75 * m_grabber_connection_len; + + m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_y_shift * Vec3d::UnitY()) * scale_transform(size)); + + offset = Vec3d(1.25 * size, grabber_y_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + offset = Vec3d(-1.25 * size, grabber_y_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + + const double xy_connection_len = 0.75 * m_grabber_connection_len; + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size)); + offset = (size + xy_connection_len) * Vec3d::UnitX(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + + if (m_groove.angle > 0.0f) { + offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size)); + offset = (size + xy_connection_len) * Vec3d::UnitY(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + } + else { + // discard transformation for CutPlaneYMove grabbers + m_raycasters[id++]->set_transform(Transform3d::Identity()); + m_raycasters[id++]->set_transform(Transform3d::Identity()); + } + } + } +} + +void GLGizmoCut3D::update_plane_model() +{ + m_plane.reset(); + on_unregister_raycasters_for_picking(); + + init_picking_models(); +} + +void GLGizmoCut3D::on_set_hover_id() +{ +} + +bool GLGizmoCut3D::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + const int object_idx = selection.get_object_idx(); + if (object_idx < 0 || selection.is_wipe_tower()) + return false; + + if (const ModelObject* mo = wxGetApp().plater()->model().objects[object_idx]; + mo->is_cut() && mo->volumes.size() == 1) { + const ModelVolume* volume = mo->volumes[0]; + if (volume->is_cut_connector() && volume->cut_info.connector_type == CutConnectorType::Dowel) + return false; + } + + // This is assumed in GLCanvas3D::do_rotate, do not change this + // without updating that function too. + return selection.is_single_full_instance() && !m_parent.is_layers_editing_enabled(); +} + +bool GLGizmoCut3D::on_is_selectable() const +{ + return wxGetApp().get_mode() != comSimple; +} + +Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3& mouse_ray) const +{ + double half_pi = 0.5 * PI; + + Transform3d m = Transform3d::Identity(); + + switch (axis) + { + case X: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); + break; + } + case Y: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + break; + } + case Z: + default: + { + // no rotation applied + break; + } + } + + m = m * m_start_dragging_m.inverse(); + m.translate(-m_plane_center); + + return transform(mouse_ray, m).intersect_plane(0.0); +} + +void GLGizmoCut3D::dragging_grabber_move(const GLGizmoBase::UpdateData &data) +{ + Vec3d starting_drag_position; + if (m_hover_id == Z) + starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * (m_grabber_connection_len * Vec3d::UnitZ()); + else + starting_drag_position = m_cut_plane_start_move_pos; + + double projection = 0.0; + + Vec3d starting_vec = m_rotation_m * (m_hover_id == CutPlaneXMove ? Vec3d::UnitX() : m_hover_id == CutPlaneYMove ? Vec3d::UnitY() : Vec3d::UnitZ()); + if (starting_vec.norm() != 0.0) { + const Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing through the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebraic form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + const Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir; // vector from the starting position to the found intersection - const Vec3d inters_vec = inters - m_drag_pos; + const Vec3d inters_vec = inters - starting_drag_position; + starting_vec.normalize(); // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); + projection = inters_vec.dot(starting_vec); } - return projection; + if (wxGetKeyState(WXK_SHIFT)) + projection = m_snap_step * std::round(projection / m_snap_step); + + const Vec3d shift = starting_vec * projection; + if (shift != Vec3d::Zero()) + reset_cut_by_contours(); + + // move cut plane center + set_center(m_plane_center + shift, true); + + m_was_cut_plane_dragged = true; } -BoundingBoxf3 GLGizmoCut::bounding_box() const +void GLGizmoCut3D::dragging_grabber_rotation(const GLGizmoBase::UpdateData &data) +{ + const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((GrabberID)m_hover_id, data.mouse_ray)); + + const Vec2d orig_dir = Vec2d::UnitX(); + const Vec2d new_dir = mouse_pos.normalized(); + + const double two_pi = 2.0 * PI; + + double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); + if (cross2(orig_dir, new_dir) < 0.0) + theta = two_pi - theta; + + const double len = mouse_pos.norm(); + // snap to coarse snap region + if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { + const double step = two_pi / double(SnapRegionsCount); + theta = step * std::round(theta / step); + } + // snap to fine snap region (scale) + else if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { + const double step = two_pi / double(ScaleStepsCount); + theta = step * std::round(theta / step); + } + + if (is_approx(theta, two_pi)) + theta = 0.0; + if (m_hover_id != Y) + theta += 0.5 * PI; + + if (!is_approx(theta, 0.0)) + reset_cut_by_contours(); + + Vec3d rotation = Vec3d::Zero(); + rotation[m_hover_id == CutPlaneZRotation ? Z : m_hover_id] = theta; + + const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation); + const bool update_tbb = !m_rotation_m.rotation().isApprox(rotation_tmp.rotation()); + m_rotation_m = rotation_tmp; + if (update_tbb) + m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m); + + m_angle = theta; + while (m_angle > two_pi) + m_angle -= two_pi; + if (m_angle < 0.0) + m_angle += two_pi; + + update_clipper(); +} + +void GLGizmoCut3D::dragging_connector(const GLGizmoBase::UpdateData &data) +{ + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + Vec3d pos; + Vec3d pos_world; + + if (unproject_on_cut_plane(data.mouse_pos.cast(), pos, pos_world)) { + connectors[m_hover_id - m_connectors_group_id].pos = pos; + update_raycasters_for_picking_transform(); + } +} + +void GLGizmoCut3D::on_dragging(const UpdateData& data) +{ + if (m_hover_id < 0) + return; + if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove || m_hover_id == CutPlaneYMove) + dragging_grabber_move(data); + else if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) + dragging_grabber_rotation(data); + else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) + dragging_connector(data); + check_and_update_connectors_state(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); +} + +void GLGizmoCut3D::on_start_dragging() +{ + m_angle = 0.0; + if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Move connector"), UndoRedo::SnapshotType::GizmoAction); + + if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) + m_start_dragging_m = m_rotation_m; +} + +void GLGizmoCut3D::on_stop_dragging() +{ + if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) { + m_angle_arc.reset(); + m_angle = 0.0; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); + m_start_dragging_m = m_rotation_m; + } + else if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove|| m_hover_id == CutPlaneYMove) { + if (m_was_cut_plane_dragged) + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); + m_ar_plane_center = m_plane_center; + } + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); + //check_and_update_connectors_state(); +} + +void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=false*/) +{ + BoundingBoxf3 tbb = m_transformed_bounding_box; + if (update_tbb) { + Vec3d normal = m_rotation_m.inverse() * Vec3d(m_plane_center - center_pos); + tbb.translate(normal.z() * Vec3d::UnitZ()); + } + + bool can_set_center_pos = false; + { + double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove.depth) : */0.5; + if (tbb.max.z() > -limit_val && tbb.min.z() < limit_val) + can_set_center_pos = true; + else { + const double old_dist = (m_bb_center - m_plane_center).norm(); + const double new_dist = (m_bb_center - center_pos).norm(); + // check if forcing is reasonable + if (new_dist < old_dist) + can_set_center_pos = true; + } + } + + if (can_set_center_pos) { + m_transformed_bounding_box = tbb; + m_plane_center = center_pos; + m_center_offset = m_plane_center - m_bb_center; + } +} + +BoundingBoxf3 GLGizmoCut3D::bounding_box() const { BoundingBoxf3 ret; const Selection& selection = m_parent.get_selection(); const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int i : idxs) { const GLVolume* volume = selection.get_volume(i); - if (!volume->is_modifier) + // respect just to the solid parts for FFF and ignore pad and supports for SLA + if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) ret.merge(volume->transformed_convex_hull_bounding_box()); } return ret; } -void GLGizmoCut::update_contours() +BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center, const Transform3d& rotation_m/* = Transform3d::Identity()*/) const { const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); - const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; - const int instance_idx = selection.get_instance_idx(); + const auto first_volume = selection.get_first_volume(); + Vec3d instance_offset = first_volume->get_instance_offset(); + instance_offset[Z] += first_volume->get_sla_shift_z(); - if (0.0 < m_cut_z && m_cut_z < m_max_z) { - if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || m_cut_contours.instance_idx != instance_idx) { - m_cut_contours.cut_z = m_cut_z; + const auto cut_matrix = Transform3d::Identity() * rotation_m.inverse() * translation_transform(instance_offset - plane_center); - if (m_cut_contours.object_id != model_object->id()) - m_cut_contours.mesh = model_object->raw_mesh(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + BoundingBoxf3 ret; + for (unsigned int i : idxs) { + const GLVolume* volume = selection.get_volume(i); + // respect just to the solid parts for FFF and ignore pad and supports for SLA + if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) { - m_cut_contours.position = box.center(); - m_cut_contours.shift = Vec3d::Zero(); - m_cut_contours.object_id = model_object->id(); - m_cut_contours.instance_idx = instance_idx; - m_cut_contours.contours.reset(); - - MeshSlicingParams slicing_params; - slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); - const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); - if (!polys.empty()) { - m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); - } - } - else if (box.center() != m_cut_contours.position) { - m_cut_contours.shift = box.center() - m_cut_contours.position; + const auto instance_matrix = volume->get_instance_transformation().get_matrix(true); + auto volume_trafo = instance_matrix * volume->get_volume_transformation().get_matrix(); + ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * volume_trafo)); } } - else - m_cut_contours.contours.reset(); + return ret; } +void GLGizmoCut3D::update_bb() +{ + const BoundingBoxf3 box = bounding_box(); + if (!box.defined) + return; + if (!m_max_pos.isApprox(box.max) || !m_min_pos.isApprox(box.min)) { + + m_bounding_box = box; + + // check, if mode is set to Planar, when object has a connectors + if (const int object_idx = m_parent.get_selection().get_object_idx(); + object_idx >= 0 && !wxGetApp().plater()->model().objects[object_idx]->cut_connectors.empty()) + m_mode = size_t(CutMode::cutPlanar); + + invalidate_cut_plane(); + reset_cut_by_contours(); + apply_color_clip_plane_colors(); + + m_max_pos = box.max; + m_min_pos = box.min; + m_bb_center = box.center(); + m_transformed_bounding_box = transformed_bounding_box(m_bb_center); + if (box.contains(m_center_offset)) + set_center_pos(m_bb_center + m_center_offset); + else + set_center_pos(m_bb_center); + + m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; + + m_radius = box.radius(); + m_grabber_connection_len = 0.5 * m_radius;// std::min(0.75 * m_radius, 35.0); + m_grabber_radius = m_grabber_connection_len * 0.85; + + m_snap_coarse_in_radius = m_grabber_radius / 3.0; + m_snap_coarse_out_radius = m_snap_coarse_in_radius * 2.; + m_snap_fine_in_radius = m_grabber_connection_len * 0.85; + m_snap_fine_out_radius = m_grabber_connection_len * 1.15; + + // input params for cut with tongue and groove + m_groove.depth = m_groove.depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box))); + m_groove.width = m_groove.width_init = 4.0f * m_groove.depth; + m_groove.flaps_angle = m_groove.flaps_angle_init = float(PI) / 3.f; + m_groove.angle = m_groove.angle_init = 0.f; + m_plane.reset(); + m_cone.reset(); + m_sphere.reset(); + m_cube.reset(); + m_grabber_connection.reset(); + m_circle.reset(); + m_scale.reset(); + m_snap_radii.reset(); + m_reference_radius.reset(); + + on_unregister_raycasters_for_picking(); + + clear_selection(); + if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info(); + selection && selection->model_object()) + m_selected.resize(selection->model_object()->cut_connectors.size(), false); + } +} + +void GLGizmoCut3D::init_picking_models() +{ + if (!m_cone.model.is_initialized()) { + indexed_triangle_set its = its_make_cone(1.0, 1.0, PI / 12.0); + m_cone.model.init_from(its); + m_cone.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } + if (!m_sphere.model.is_initialized()) { + indexed_triangle_set its = its_make_sphere(1.0, PI / 12.0); + m_sphere.model.init_from(its); + m_sphere.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } + if (!m_cube.model.is_initialized()) { + indexed_triangle_set its = its_make_cube(1., 1., 1.); + m_cube.model.init_from(its); + m_cube.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } + + if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { + const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box); + indexed_triangle_set its = m_mode == size_t(CutMode::cutTongueAndGroove) ? its_make_groove_plane() : + its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); + + m_plane.model.init_from(its); + m_plane.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } + + if (m_shapes.empty()) + init_connector_shapes(); +} + +void GLGizmoCut3D::init_rendering_items() +{ + if (!m_grabber_connection.is_initialized()) + m_grabber_connection.init_from(its_make_line(Vec3f::Zero(), Vec3f::UnitZ())); + if (!m_circle.is_initialized()) + init_from_circle(m_circle, m_grabber_radius); + if (!m_scale.is_initialized()) + init_from_scale(m_scale, m_grabber_radius); + if (!m_snap_radii.is_initialized()) + init_from_snap_radii(m_snap_radii, m_grabber_radius); + if (!m_reference_radius.is_initialized()) { + m_reference_radius.init_from(its_make_line(Vec3f::Zero(), m_grabber_connection_len * Vec3f::UnitX())); + m_reference_radius.set_color(ColorRGBA::WHITE()); + } + if (!m_angle_arc.is_initialized() || m_angle != 0.0) + init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); +} + +void GLGizmoCut3D::render_clipper_cut() +{ + if (! m_connectors_editing) + ::glDisable(GL_DEPTH_TEST); + + GLboolean cull_face = GL_FALSE; + ::glGetBooleanv(GL_CULL_FACE, &cull_face); + ::glDisable(GL_CULL_FACE); + m_c->object_clipper()->render_cut(m_part_selection.get_ignored_contours_ptr()); + if (cull_face) + ::glEnable(GL_CULL_FACE); + + if (! m_connectors_editing) + ::glEnable(GL_DEPTH_TEST); +} + +void GLGizmoCut3D::PartSelection::add_object(const ModelObject* object) +{ + m_model = Model(); + m_model.add_object(*object); + + const double sla_shift_z = wxGetApp().plater()->canvas3D()->get_selection().get_first_volume()->get_sla_shift_z(); + if (!is_approx(sla_shift_z, 0.)) { + Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset(); + inst_offset[Z] += sla_shift_z; + model_object()->instances[m_instance_idx]->set_offset(inst_offset); + } +} + + +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) + : m_instance_idx(instance_idx_in) +{ + Cut cut(mo, instance_idx_in, cut_matrix); + add_object(cut.perform_with_plane().front()); + + const ModelVolumePtrs& volumes = model_object()->volumes; + + // split to parts + for (int id = int(volumes.size())-1; id >= 0; id--) + if (volumes[id]->is_splittable()) + volumes[id]->split(1); + + m_parts.clear(); + for (const ModelVolume* volume : volumes) { + assert(volume != nullptr); + m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true, !volume->is_model_part()}); + m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f }); + m_parts.back().glmodel.init_from(volume->mesh()); + + // Now check whether this part is below or above the plane. + Transform3d tr = (model_object()->instances[m_instance_idx]->get_matrix() * volume->get_matrix()).inverse(); + Vec3f pos = (tr * center).cast(); + Vec3f norm = (tr.linear().inverse().transpose() * normal).cast(); + for (const Vec3f& v : volume->mesh().its.vertices) { + double p = (v - pos).dot(norm); + if (std::abs(p) > EPSILON) { + m_parts.back().selected = p > 0.; + break; + } + } + } + + // Now go through the contours and create a map from contours to parts. + m_contour_points.clear(); + m_contour_to_parts.clear(); + m_debug_pts = std::vector>(m_parts.size(), std::vector()); + if (std::vector pts = oc.point_per_contour();! pts.empty()) { + + m_contour_to_parts.resize(pts.size()); + + for (size_t pt_idx=0; pt_idxinstances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[part_id]->get_offset())).inverse(); + for (double d : {-1., 1.}) { + const Vec3d dir_mesh = d * tr.linear().inverse().transpose() * normal; + const Vec3d src = tr * (m_contour_points[pt_idx] + d*0.01 * normal); + AABBMesh::hit_result hit = aabb.query_ray_hit(src, dir_mesh); + + m_debug_pts[part_id].emplace_back(src); + + if (hit.is_inside()) { + // This part belongs to this point. + if (d == 1.) + m_contour_to_parts[pt_idx].first.emplace_back(part_id); + else + m_contour_to_parts[pt_idx].second.emplace_back(part_id); + } + } + } + } + + } + + + m_valid = true; +} + +// In CutMode::cutTongueAndGroove we use PartSelection just for rendering +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* object, int instance_idx_in) + : m_instance_idx (instance_idx_in) +{ + add_object(object); + + m_parts.clear(); + + for (const ModelVolume* volume : object->volumes) { + assert(volume != nullptr); + m_parts.emplace_back(Part{ GLModel(), MeshRaycaster(volume->mesh()), true, !volume->is_model_part() }); + m_parts.back().glmodel.init_from(volume->mesh()); + + // Now check whether this part is below or above the plane. + m_parts.back().selected = volume->is_from_upper(); + } + + m_valid = true; +} + +void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_model) +{ + if (! valid()) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + + if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) { + shader->start_using(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("emission_factor", 0.f); + + // FIXME: Cache the transforms. + + const Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset(); + const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset); + + const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05; + + for (size_t id=0; idset_uniform("view_model_matrix", view_inst_matrix * model_object()->volumes[id]->get_matrix()); + if (m_parts[id].is_modifier) { + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + } + m_parts[id].glmodel.set_color(m_parts[id].is_modifier ? MODIFIER_COLOR : (m_parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR)); + m_parts[id].glmodel.render(); + if (m_parts[id].is_modifier) + glsafe(::glDisable(GL_BLEND)); + } + + shader->stop_using(); + } + + + + // { // Debugging render: + + // static int idx = -1; + // ImGui::Begin("DEBUG"); + // for (int i=0; i= m_parts.size()) + // idx = -1; + // ImGui::End(); + + // ::glDisable(GL_DEPTH_TEST); + // if (valid()) { + // for (size_t i=0; i GLGizmoCut3D::PartSelection::get_cut_parts() +{ + std::vector parts; + + for (const auto& part : m_parts) + parts.push_back({part.selected, part.is_modifier}); + + return parts; +} + + +void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) +{ + // FIXME: Cache the transforms. + const Camera& camera = wxGetApp().plater()->get_camera(); + const Vec3d& camera_pos = camera.get_position(); + + Vec3f pos; + Vec3f normal; + + std::vector> hits_id_and_sqdist; + + for (size_t id=0; idvolumes[id]->get_offset(); + Transform3d tr = translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[id]->get_offset()); + if (m_parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { + hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast())).squaredNorm()); + } + } + if (! hits_id_and_sqdist.empty()) { + size_t id = std::min_element(hits_id_and_sqdist.begin(), hits_id_and_sqdist.end(), + [](const std::pair& a, const std::pair& b) { return a.second < b.second; })->first; + m_parts[id].selected = ! m_parts[id].selected; + + // And now recalculate the contours which should be ignored. + m_ignored_contours.clear(); + size_t cont_id = 0; + for (const auto& [parts_above, parts_below] : m_contour_to_parts) { + for (size_t upper : parts_above) { + bool upper_sel = m_parts[upper].selected; + if (std::find_if(parts_below.begin(), parts_below.end(), [this, &upper_sel](const size_t& i) { return m_parts[i].selected == upper_sel; }) != parts_below.end()) { + m_ignored_contours.emplace_back(cont_id); + break; + } + } + ++cont_id; + } + } +} + +void GLGizmoCut3D::PartSelection::turn_over_selection() +{ + for (Part& part : m_parts) + part.selected = !part.selected; +} + +void GLGizmoCut3D::on_render() +{ + if (m_state == On) { + // This gizmo is showing the object elevated. Tell the common + // SelectionInfo object to lie about the actual shift. + //m_c->selection_info()->set_use_shift(true); + } + + // check objects visibility + toggle_model_objects_visibility(); + + update_clipper(); + + init_picking_models(); + + init_rendering_items(); + + render_connectors(); + + if (!m_connectors_editing) + m_part_selection.render(nullptr, m_sphere.model); + else + m_part_selection.render(&m_cut_normal, m_sphere.model); + + render_clipper_cut(); + + if (!m_hide_cut_plane && !m_connectors_editing) { + render_cut_plane(); + render_cut_plane_grabbers(); + } + + render_cut_line(); + + m_selection_rectangle.render(m_parent); +} + +void GLGizmoCut3D::render_debug_input_window(float x) +{ + return; + m_imgui->begin(wxString("DEBUG")); + + m_imgui->end(); +/* + static bool hide_clipped = false; + static bool fill_cut = false; + static float contour_width = 0.4f; + + m_imgui->checkbox(_L("Hide cut plane and grabbers"), m_hide_cut_plane); + if (m_imgui->checkbox("hide_clipped", hide_clipped) && !hide_clipped) + m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); + m_imgui->checkbox("fill_cut", fill_cut); + m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); + if (auto oc = m_c->object_clipper()) + oc->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, double(contour_width)); +*/ + ImGui::PushItemWidth(0.5f * m_label_width); + if (auto oc = m_c->object_clipper(); oc && m_imgui->slider_float("contour_width", &m_contour_width, 0.f, 3.f)) + oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); + + ImGui::Separator(); + + if (m_imgui->checkbox(("Render cut plane as disc"), m_cut_plane_as_circle)) + m_plane.reset(); + + ImGui::PushItemWidth(0.5f * m_label_width); + if (m_imgui->slider_float("cut_plane_radius_koef", &m_cut_plane_radius_koef, 1.f, 2.f)) + m_plane.reset(); + + m_imgui->end(); +} + +void GLGizmoCut3D::unselect_all_connectors() +{ + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected_count = 0; + validate_connector_settings(); +} + +void GLGizmoCut3D::select_all_connectors() +{ + std::fill(m_selected.begin(), m_selected.end(), true); + m_selected_count = int(m_selected.size()); +} + +void GLGizmoCut3D::apply_selected_connectors(std::function apply_fn) +{ + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) + apply_fn(idx); + check_and_update_connectors_state(); + update_raycasters_for_picking_transform(); +} + +void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, float x, float y, float bottom_limit) +{ + // Connectors section + + ImGui::Separator(); + + // WIP : Auto : Need to implement + // m_imgui->text(_L("Mode")); + // render_connect_mode_radio_button(CutConnectorMode::Auto); + // render_connect_mode_radio_button(CutConnectorMode::Manual); + + ImGui::AlignTextToFramePadding(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Connectors"]); + + m_imgui->disabled_begin(connectors.empty()); + ImGui::SameLine(m_label_width); + const std::string act_name = _u8L("Remove connectors"); + if (render_reset_button("connectors", act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); + reset_connectors(); + } + m_imgui->disabled_end(); + + render_flip_plane_button(m_connectors_editing && connectors.empty()); + + m_imgui->text(m_labels_map["Type"]); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.00f, 0.00f, 1.00f)); + bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); + type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); + type_changed |= render_connect_type_radio_button(CutConnectorType::Snap); + if (type_changed) + apply_selected_connectors([this, &connectors] (size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); }); + ImGui::PopStyleColor(1); + + m_imgui->disabled_begin(m_connector_type != CutConnectorType::Plug); + if (type_changed && m_connector_type == CutConnectorType::Dowel) { + m_connector_style = int(CutConnectorStyle::Prism); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + } + if (render_combo(m_labels_map["Style"], m_connector_styles, m_connector_style)) + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Snap); + if (type_changed && m_connector_type == CutConnectorType::Snap) { + m_connector_shape_id = int(CutConnectorShape::Circle); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + } + if (render_combo(m_labels_map["Shape"], m_connector_shapes, m_connector_shape_id)) + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + m_imgui->disabled_end(); + + const float depth_min_value = m_connector_type == CutConnectorType::Snap ? m_connector_size : -0.1f; + if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) + apply_selected_connectors([this, &connectors](size_t idx) { + if (m_connector_depth_ratio > 0) + connectors[idx].height = m_connector_depth_ratio; + if (m_connector_depth_ratio_tolerance >= 0) + connectors[idx].height_tolerance = m_connector_depth_ratio_tolerance; + }); + + if (render_slider_double_input(m_labels_map["Size"], m_connector_size, m_connector_size_tolerance)) + apply_selected_connectors([this, &connectors](size_t idx) { + if (m_connector_size > 0) + connectors[idx].radius = 0.5f * m_connector_size; + if (m_connector_size_tolerance >= 0) + connectors[idx].radius_tolerance = 0.5f * m_connector_size_tolerance; + }); + + if (render_angle_input(m_labels_map["Rotation"], m_connector_angle, 0.f, 0.f, 180.f)) + apply_selected_connectors([this, &connectors](size_t idx) { + connectors[idx].z_angle = m_connector_angle; + }); + + if (m_connector_type == CutConnectorType::Snap) { + render_snap_specific_input(_u8L("Bulge"), _L("Bulge proportion related to radius"), m_snap_bulge_proportion, 0.15f, 5.f, 100.f * m_snap_space_proportion); + render_snap_specific_input(_u8L("Space"), _L("Space proportion related to radius"), m_snap_space_proportion, 0.3f, 10.f, 50.f); + } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + show_tooltip_information(x, get_cur_y); + + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + + ImGui::SameLine(); + if (m_imgui->button(_L("Confirm connectors"))) { + unselect_all_connectors(); + set_connectors_editing(false); + } + + ImGui::SameLine(m_label_width + m_editing_window_width - m_imgui->calc_text_size(_L("Cancel")).x - m_imgui->get_style_scaling() * 8); + + if (m_imgui->button(_L("Cancel"))) { + reset_connectors(); + set_connectors_editing(false); + } + + ImGui::PopStyleVar(2); +} + +void GLGizmoCut3D::render_build_size() +{ + double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0; + wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); + + Vec3d tbb_sz = m_transformed_bounding_box.size(); + wxString size = "X: " + double_to_string(tbb_sz.x() * koef, 2) + unit_str + + ", Y: " + double_to_string(tbb_sz.y() * koef, 2) + unit_str + + ", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Build Volume")); + ImGui::SameLine(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); +} + +void GLGizmoCut3D::reset_cut_plane() +{ + m_angle_arc.reset(); + m_transformed_bounding_box = transformed_bounding_box(m_bb_center); + set_center(m_bb_center); + m_start_dragging_m = m_rotation_m = Transform3d::Identity(); + m_ar_plane_center = m_plane_center; + + reset_cut_by_contours(); + m_parent.request_extra_frame(); +} + +void GLGizmoCut3D::invalidate_cut_plane() +{ + m_rotation_m = Transform3d::Identity(); + m_plane_center = Vec3d::Zero(); + m_min_pos = Vec3d::Zero(); + m_max_pos = Vec3d::Zero(); + m_bb_center = Vec3d::Zero(); + m_center_offset = Vec3d::Zero(); +} + +void GLGizmoCut3D::set_connectors_editing(bool connectors_editing) +{ + if (m_connectors_editing == connectors_editing) + return; + + m_connectors_editing = connectors_editing; + update_raycasters_for_picking(); + + m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); + + m_parent.request_extra_frame(); +} + +void GLGizmoCut3D::flip_cut_plane() +{ + m_rotation_m = m_rotation_m * rotation_transform(PI * Vec3d::UnitX()); + m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Flip cut plane"), UndoRedo::SnapshotType::GizmoAction); + m_start_dragging_m = m_rotation_m; + + update_clipper(); + m_part_selection.turn_over_selection(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); +} + +void GLGizmoCut3D::reset_cut_by_contours() +{ + m_part_selection = PartSelection(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (m_dragging || m_groove_editing || !has_valid_groove()) + return; + process_contours(); + } + else + toggle_model_objects_visibility(); +} + +void GLGizmoCut3D::process_contours() +{ + const Selection& selection = m_parent.get_selection(); + const ModelObjectPtrs& model_objects = selection.get_model()->objects; + + const int instance_idx = selection.get_instance_idx(); + if (instance_idx < 0) + return; + const int object_idx = selection.get_object_idx(); + + wxBusyCursor wait; + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (has_valid_groove()) { + Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection)); + const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true); + if (!new_objects.empty()) + m_part_selection = PartSelection(new_objects.front(), instance_idx); + } + } + else { + reset_cut_by_contours(); + m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); + } + + toggle_model_objects_visibility(); +} + +void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/) +{ + ImGui::SameLine(); + + if (m_hover_id == CutPlane) + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); + + m_imgui->disabled_begin(disable_pred); + if (m_imgui->button(_L("Flip cut plane"))) + flip_cut_plane(); + m_imgui->disabled_end(); + + if (m_hover_id == CutPlane) + ImGui::PopStyleColor(); +} + +void GLGizmoCut3D::add_vertical_scaled_interval(float interval) +{ + ImGui::GetCurrentWindow()->DC.CursorPos.y += m_imgui->scaled(interval); +} + +void GLGizmoCut3D::add_horizontal_scaled_interval(float interval) +{ + ImGui::GetCurrentWindow()->DC.CursorPos.x += m_imgui->scaled(interval); +} + +void GLGizmoCut3D::add_horizontal_shift(float shift) +{ + ImGui::GetCurrentWindow()->DC.CursorPos.x += shift; +} + +void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) +{ + ImGui::SameLine(); + const float radius = 0.5f * size; + ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; + pos.x += size; + pos.y += 1.25f * radius; + ImGui::GetCurrentWindow()->DrawList->AddNgonFilled(pos, radius, color, 6); + m_imgui->text(" "); +} + +void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) +{ + bool is_changed{false}; + + float val = in_val; + float tolerance = in_tolerance; + if (render_slider_double_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _u8L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + m_groove_editing = true; + } + in_val = val; + in_tolerance = tolerance; + is_changed = true; + } + + ImGui::SameLine(); + + m_imgui->disabled_begin(is_approx(in_val, init_val) && is_approx(in_tolerance, 0.1f)); + const std::string act_name = _u8L("Reset"); + if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + in_tolerance = 0.1f; + is_changed = true; + } + m_imgui->disabled_end(); + + if (is_changed) { + update_plane_model(); + reset_cut_by_contours(); + } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } +} + +bool GLGizmoCut3D::render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val) +{ + // -------- [ ] + // slider_with + item_in_gap + input_width + double slider_with = 0.24 * m_editing_window_width; // m_control_width * 0.35; + double item_in_gap = 0.01 * m_editing_window_width; + double input_width = 0.29 * m_editing_window_width; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(slider_with); + + double left_width = m_label_width + slider_with + item_in_gap; + + bool is_changed{ false }; + + float val = rad2deg(in_val); + const float old_val = val; + + const std::string format = "%.0f " + _u8L("°"); + m_imgui->bbl_slider_float_style(("##angle_" + label).c_str(), &val, min_val, max_val, format.c_str(), 1.f, true, from_u8(label)); + + ImGui::SameLine(left_width); + ImGui::PushItemWidth(input_width); + ImGui::BBLDragFloat(("##angle_input_" + label).c_str(), &val, 0.05f, min_val, max_val, format.c_str()); + + m_is_slider_editing_done |= m_imgui->get_last_slider_status().deactivated_after_edit; + if (!is_approx(old_val, val)) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + // TRN: This is an entry in the Undo/Redo stack. The whole line will be 'Edited: (name of whatever was edited)'. + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _L("Edited"), label), UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + if (m_mode == size_t(CutMode::cutTongueAndGroove)) + m_groove_editing = true; + } + in_val = deg2rad(val); + is_changed = true; + } + + ImGui::SameLine(); + + m_imgui->disabled_begin(is_approx(in_val, init_val)); + const std::string act_name = _u8L("Reset"); + if (render_reset_button(("##angle_" + label + act_name).c_str(), act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + is_changed = true; + } + m_imgui->disabled_end(); + + return is_changed; +} + +void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val) +{ + if (render_angle_input(label, in_val, init_val, min_val, max_val)) { + update_plane_model(); + reset_cut_by_contours(); + } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } +} + +void GLGizmoCut3D::render_snap_specific_input(const std::string& label, const wxString& tooltip, float& in_val, const float& init_val, const float min_val, const float max_val) +{ + // -------- [ ] + // slider_with + item_in_gap + input_width + double slider_with = 0.24 * m_editing_window_width; // m_control_width * 0.35; + double item_in_gap = 0.01 * m_editing_window_width; + double input_width = 0.29 * m_editing_window_width; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(slider_with); + + double left_width = m_label_width + slider_with + item_in_gap; + + bool is_changed = false; + const std::string format = "%.0f %%"; + + float val = in_val * 100.f; + const float old_val = val; + m_imgui->bbl_slider_float_style(("##snap_" + label).c_str(), &val, min_val, max_val, format.c_str(), 1.f, true, tooltip); + + ImGui::SameLine(left_width); + ImGui::PushItemWidth(input_width); + ImGui::BBLDragFloat(("##snap_input_" + label).c_str(), &val, 0.05f, min_val, max_val, format.c_str()); + + if (!is_approx(old_val, val)) { + in_val = val * 0.01f; + is_changed = true; + } + + ImGui::SameLine(); + + m_imgui->disabled_begin(is_approx(in_val, init_val)); + const std::string act_name = _u8L("Reset"); + if (render_reset_button(("##snap_" + label + act_name).c_str(), act_name)) { + in_val = init_val; + is_changed = true; + } + m_imgui->disabled_end(); + + if (is_changed) { + update_connector_shape(); + update_raycasters_for_picking(); + } +} + +void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit) +{ +// if (m_mode == size_t(CutMode::cutPlanar)) { + CutMode mode = CutMode(m_mode); + if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) { + const bool has_connectors = !connectors.empty(); + + m_imgui->disabled_begin(has_connectors); + if (render_cut_mode_combo()) + mode = CutMode(m_mode); + m_imgui->disabled_end(); + + render_build_size(); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Cut position") + ": "); + ImGui::SameLine(); + render_move_center_input(Z); + ImGui::SameLine(); + + const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); + m_imgui->disabled_begin(is_cut_plane_init); + std::string act_name = _u8L("Reset cutting plane"); + if (render_reset_button("cut_plane", into_u8(act_name))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); + reset_cut_plane(); + } + m_imgui->disabled_end(); + +// render_flip_plane_button(); + + if (mode == CutMode::cutPlanar) { + add_vertical_scaled_interval(0.75f); + + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); + if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) + set_connectors_editing(true); + m_imgui->disabled_end(); + + ImGui::SameLine(1.5f * m_control_width); + + m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); + act_name = _u8L("Reset cut"); + if (m_imgui->button(act_name, _u8L("Reset cutting plane and remove connectors"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); + reset_cut_plane(); + reset_connectors(); + } + m_imgui->disabled_end(); + } + else if (mode == CutMode::cutTongueAndGroove) { + m_is_slider_editing_done = false; + ImGui::Separator(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": "); + render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); + render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); + render_groove_angle_input(m_labels_map["Flap Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f); + render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f); + } + + ImGui::Separator(); + + // render "After Cut" section + + float label_width = 0; + for (const wxString &label : {_L("Upper part"), _L("Lower part")}) { + const float width = m_imgui->calc_text_size(label).x + m_imgui->scaled(1.5f); + if (label_width < width) + label_width = width; + } + + auto render_part_action_line = [this, label_width, &connectors](const wxString &label, const wxString &suffix, bool &keep_part, + bool &place_on_cut_part, bool &rotate_part) { + bool keep = true; + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + + ImGui::SameLine(label_width); + + m_imgui->disabled_begin(!connectors.empty() || m_keep_as_parts); + m_imgui->bbl_checkbox(_L("Keep") + suffix, connectors.empty() ? keep_part : keep); + m_imgui->disabled_end(); + + ImGui::SameLine(); + + m_imgui->disabled_begin(!keep_part || m_keep_as_parts); + if (m_imgui->bbl_checkbox(_L("Place on cut") + suffix, place_on_cut_part)) + rotate_part = false; + ImGui::SameLine(); + if (m_imgui->bbl_checkbox(_L("Flip") + suffix, rotate_part)) + place_on_cut_part = false; + m_imgui->disabled_end(); + }; + + m_imgui->text(_L("After cut") + ": "); + render_part_action_line(_L("Upper part"), "##upper", m_keep_upper, m_place_on_cut_upper, m_rotate_upper); + render_part_action_line(_L("Lower part"), "##lower", m_keep_lower, m_place_on_cut_lower, m_rotate_lower); + + m_imgui->disabled_begin(has_connectors); + m_imgui->bbl_checkbox(_L("Cut to parts"), m_keep_as_parts); + if (m_keep_as_parts) { + m_keep_upper = true; + m_keep_lower = true; + } + m_imgui->disabled_end(); + } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + show_tooltip_information(x, get_cur_y); + + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + + ImGui::SameLine(); + m_imgui->disabled_begin(!can_perform_cut()); + if(m_imgui->button(_L("Perform cut"))) + perform_cut(m_parent.get_selection()); + m_imgui->disabled_end(); + + ImGui::PopStyleVar(2); +} + +void GLGizmoCut3D::validate_connector_settings() +{ + if (m_connector_depth_ratio < 0.f) + m_connector_depth_ratio = 3.f; + if (m_connector_depth_ratio_tolerance < 0.f) + m_connector_depth_ratio_tolerance = 0.1f; + if (m_connector_size < 0.f) + m_connector_size = 2.5f; + if (m_connector_size_tolerance < 0.f) + m_connector_size_tolerance = 0.f; + if (m_connector_angle < 0.f || m_connector_angle > float(PI) ) + m_connector_angle = 0.f; + + if (m_connector_type == CutConnectorType::Undef) + m_connector_type = CutConnectorType::Plug; + if (m_connector_style == int(CutConnectorStyle::Undef)) + m_connector_style = int(CutConnectorStyle::Prism); + if (m_connector_shape_id == int(CutConnectorShape::Undef)) + m_connector_shape_id = int(CutConnectorShape::Circle); +} + +void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) +{ + m_imperial_units = wxGetApp().app_config->get_bool("use_inches"); + m_control_width = m_imgui->get_font_size() * 9.f; + + m_editing_window_width = 1.45 * m_control_width + 11; + + if (m_connectors_editing && m_selected_count > 0) { + float depth_ratio { UndefFloat }; + float depth_ratio_tolerance { UndefFloat }; + float radius { UndefFloat }; + float radius_tolerance { UndefFloat }; + float angle { UndefFloat }; + CutConnectorType type { CutConnectorType::Undef }; + CutConnectorStyle style { CutConnectorStyle::Undef }; + CutConnectorShape shape { CutConnectorShape::Undef }; + + bool is_init = false; + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) { + const CutConnector& connector = connectors[idx]; + if (!is_init) { + depth_ratio = connector.height; + depth_ratio_tolerance = connector.height_tolerance; + radius = connector.radius; + radius_tolerance = connector.radius_tolerance; + angle = connector.z_angle; + type = connector.attribs.type; + style = connector.attribs.style; + shape = connector.attribs.shape; + + if (m_selected_count == 1) + break; + is_init = true; + } + else { + if (!is_approx(depth_ratio, connector.height)) + depth_ratio = UndefFloat; + if (!is_approx(depth_ratio_tolerance, connector.height_tolerance)) + depth_ratio_tolerance = UndefFloat; + if (!is_approx(radius,connector.radius)) + radius = UndefFloat; + if (!is_approx(radius_tolerance, connector.radius_tolerance)) + radius_tolerance = UndefFloat; + if (!is_approx(angle, connector.z_angle)) + angle = UndefFloat; + + if (type != connector.attribs.type) + type = CutConnectorType::Undef; + if (style != connector.attribs.style) + style = CutConnectorStyle::Undef; + if (shape != connector.attribs.shape) + shape = CutConnectorShape::Undef; + } + } + + m_connector_depth_ratio = depth_ratio; + m_connector_depth_ratio_tolerance = depth_ratio_tolerance; + m_connector_size = 2.f * radius; + m_connector_size_tolerance = 2.f * radius_tolerance; + m_connector_type = type; + m_connector_angle = angle; + m_connector_style = int(style); + m_connector_shape_id = int(shape); + } + + if (m_label_width == 0.f) { + for (const auto& item : m_labels_map) { + const float width = m_imgui->calc_text_size(item.second).x; + if (m_label_width < width) + m_label_width = width; + } + m_label_width += m_imgui->scaled(1.f); + m_label_width += ImGui::GetStyle().WindowPadding.x; + } +} + +void GLGizmoCut3D::render_input_window_warning() const +{ + if (! m_invalid_connectors_idxs.empty()) { + wxString out = /*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Invalid connectors detected") + ":"; + if (m_info_stats.outside_cut_contour > size_t(0)) + out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour), + m_info_stats.outside_cut_contour); + if (m_info_stats.outside_bb > size_t(0)) + out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of object", "%1$d connectors are out of object", m_info_stats.outside_bb), + m_info_stats.outside_bb); + if (m_info_stats.is_overlap) + out += "\n - " + _L("Some connectors are overlapped"); + m_imgui->text(out); + } + if (!m_keep_upper && !m_keep_lower) + m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Select at least one object to keep after cutting.")); + if (!has_valid_contour()) + m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane is placed out of object")); + else if (!has_valid_groove()) + m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane with groove is invalid")); +} + +void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) +{ + GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + + init_input_window_data(connectors); + + if (m_connectors_editing) // connectors mode + render_connectors_input_window(connectors, x, y, bottom_limit); + else + render_cut_plane_input_window(connectors, x, y, bottom_limit); + + render_input_window_warning(); + + GizmoImguiEnd(); + + // Orca + ImGuiWrapper::pop_toolbar_style(); + + if (!m_connectors_editing) // connectors mode + render_debug_input_window(x); +} + +void GLGizmoCut3D::show_tooltip_information(float x, float y) +{ + auto &shortcuts = m_connectors_editing ? m_shortcuts_connector : m_shortcuts_cut; + + float caption_max = 0.f; + for (const auto &short_cut : shortcuts) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(short_cut.first).x); + } + + ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += m_imgui->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { + m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto &short_cut : shortcuts) + draw_text_with_caption(short_cut.first + ": ", short_cut.second); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +{ + // check if connector pos is out of clipping plane + if (m_c->object_clipper() && m_c->object_clipper()->is_projection_inside_cut(cur_pos) == -1) { + m_info_stats.outside_cut_contour++; + return true; + } + + // check if connector bottom contour is out of clipping plane + const CutConnector& cur_connector = connectors[idx]; + const CutConnectorShape shape = CutConnectorShape(cur_connector.attribs.shape); + const int sectorCount = shape == CutConnectorShape::Triangle ? 3 : + shape == CutConnectorShape::Square ? 4 : + shape == CutConnectorShape::Circle ? 60: // supposably, 60 points are enough for conflict detection + shape == CutConnectorShape::Hexagon ? 6 : 1 ; + + indexed_triangle_set mesh; + auto& vertices = mesh.vertices; + vertices.reserve(sectorCount + 1); + + float fa = 2 * PI / sectorCount; + auto vec = Eigen::Vector2f(0, cur_connector.radius); + for (float angle = 0; angle < 2.f * PI; angle += fa) { + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); + } + its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); + + for (const Vec3f& vertex : vertices) { + if (m_c->object_clipper()) { + int contour_idx = m_c->object_clipper()->is_projection_inside_cut(vertex.cast()); + bool is_invalid = (contour_idx == -1); + if (m_part_selection.valid() && ! is_invalid) { + assert(contour_idx >= 0); + const std::vector& ignored = *(m_part_selection.get_ignored_contours_ptr()); + is_invalid = (std::find(ignored.begin(), ignored.end(), size_t(contour_idx)) != ignored.end()); + } + if (is_invalid) { + m_info_stats.outside_cut_contour++; + return true; + } + } + } + + return false; +} + +bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +{ + if (is_outside_of_cut_contour(idx, connectors, cur_pos)) + return true; + + const CutConnector& cur_connector = connectors[idx]; + + const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m * + scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); + const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); + + // check if connector's bounding box is inside the object's bounding box + if (!m_bounding_box.contains(cur_tbb)) { + m_info_stats.outside_bb++; + return true; + } + + // check if connectors are overlapping + for (size_t i = 0; i < connectors.size(); ++i) { + if (i == idx) + continue; + const CutConnector& connector = connectors[i]; + + if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) { + m_info_stats.is_overlap = true; + return true; + } + } + + return false; +} + +void GLGizmoCut3D::check_and_update_connectors_state() +{ + m_info_stats.invalidate(); + m_invalid_connectors_idxs.clear(); + if (CutMode(m_mode) != CutMode::cutPlanar) + return; + const ModelObject* mo = m_c->selection_info()->model_object(); + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + const CutConnectors& connectors = mo->cut_connectors; + const ModelInstance* mi = mo->instances[inst_id]; + const Vec3d& instance_offset = mi->get_offset(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + + for (size_t i = 0; i < connectors.size(); ++i) { + const CutConnector& connector = connectors[i]; + Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // recalculate connector position to world position + if (is_conflict_for_connector(i, connectors, pos)) + m_invalid_connectors_idxs.emplace_back(i); + } +} + +void GLGizmoCut3D::toggle_model_objects_visibility() +{ + bool has_active_volume = false; + std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + for (const std::shared_ptr &raycaster : *raycasters) + if (raycaster->is_active()) { + has_active_volume = true; + break; + } + + if (m_part_selection.valid() && has_active_volume) + m_parent.toggle_model_objects_visibility(false); + else if (!m_part_selection.valid() && !has_active_volume) { + const Selection& selection = m_parent.get_selection(); + const ModelObjectPtrs& model_objects = selection.get_model()->objects; + m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); + } +} + +void GLGizmoCut3D::render_connectors() +{ + ::glEnable(GL_DEPTH_TEST); + + if (cut_line_processing() || + CutMode(m_mode) != CutMode::cutPlanar || + m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) + return; + + const ModelObject* mo = m_c->selection_info()->model_object(); + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + const CutConnectors& connectors = mo->cut_connectors; + if (connectors.size() != m_selected.size()) { + // #ysFIXME + clear_selection(); + m_selected.resize(connectors.size(), false); + } + + ColorRGBA render_color = CONNECTOR_DEF_COLOR; + + const ModelInstance* mi = mo->instances[inst_id]; + const Vec3d& instance_offset = mi->get_offset(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + + const bool looking_forward = is_looking_forward(); + + for (size_t i = 0; i < connectors.size(); ++i) { + const CutConnector& connector = connectors[i]; + + float height = connector.height; + // recalculate connector position to world position + Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); + + // First decide about the color of the point. + assert(std::is_sorted(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end())); + const bool conflict_connector = std::binary_search(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end(), i); + if (conflict_connector) + render_color = CONNECTOR_ERR_COLOR; + else // default connector color + render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR; + + if (!m_connectors_editing) + render_color = CONNECTOR_ERR_COLOR; + else if (size_t(m_hover_id - m_connectors_group_id) == i) + render_color = conflict_connector ? HOVERED_ERR_COLOR : + connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; + else if (m_selected[i]) + render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; + + const Camera& camera = wxGetApp().plater()->get_camera(); + if (connector.attribs.type == CutConnectorType::Dowel && + connector.attribs.style == CutConnectorStyle::Prism) { + if (m_connectors_editing) { + height = 0.05f; + if (!looking_forward) + pos += 0.05 * m_clp_normal; + } + else { + if (looking_forward) + pos -= static_cast(height) * m_clp_normal; + else + pos += static_cast(height) * m_clp_normal; + height *= 2; + } + } + else if (!looking_forward) + pos += 0.05 * m_clp_normal; + + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * + rotation_transform(-connector.z_angle * Vec3d::UnitZ()) * + scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); + + render_model(m_shapes[connector.attribs].model, render_color, view_model_matrix); + } +} + +bool GLGizmoCut3D::can_perform_cut() const +{ + if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) + return false; + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return has_valid_groove(); + + if (m_part_selection.valid()) + return ! m_part_selection.is_one_object(); + + return true; +} + +bool GLGizmoCut3D::has_valid_groove() const +{ + if (CutMode(m_mode) != CutMode::cutTongueAndGroove) + return true; + + const float flaps_width = -2.f * m_groove.depth / tan(m_groove.flaps_angle); + if (flaps_width > m_groove.width) + return false; + + const Selection& selection = m_parent.get_selection(); + const auto&list = selection.get_volume_idxs(); + // is more volumes selected? + if (list.empty()) + return false; + + const Transform3d cp_matrix = translation_transform(m_plane_center) * m_rotation_m; + + for (size_t id = 0; id < m_groove_vertices.size(); id += 2) { + const Vec3d beg = cp_matrix * m_groove_vertices[id]; + const Vec3d end = cp_matrix * m_groove_vertices[id + 1]; + + bool intersection = false; + for (const unsigned int volume_idx : list) { + const GLVolume* glvol = selection.get_volume(volume_idx); + if (!glvol->is_modifier && + glvol->mesh_raycaster->intersects_line(beg, end - beg, glvol->world_matrix())) { + intersection = true; + break; + } + } + if (!intersection) + return false; + } + + return true; +} + +bool GLGizmoCut3D::has_valid_contour() const +{ + const auto clipper = m_c->object_clipper(); + return clipper && clipper->has_valid_contour(); +} + +void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count) +{ + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return; + if (m_connector_mode == CutConnectorMode::Manual) { + clear_selection(); + + for (CutConnector&connector : mo->cut_connectors) { + connector.rotation_m = m_rotation_m; + + if (connector.attribs.type == CutConnectorType::Dowel) { + if (connector.attribs.style == CutConnectorStyle::Prism) + connector.height *= 2; + dowels_count ++; + } + else { + // calculate shift of the connector center regarding to the position on the cut plane + connector.pos += m_cut_normal * 0.5 * double(connector.height); + } + } + apply_cut_connectors(mo, _u8L("Connector")); + } +} + +Transform3d GLGizmoCut3D::get_cut_matrix(const Selection& selection) +{ + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + ModelObject* mo = selection.get_model()->objects[object_idx]; + if (!mo) + return Transform3d::Identity(); + + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. + const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z(); + + const Vec3d instance_offset = mo->instances[instance_idx]->get_offset(); + Vec3d cut_center_offset = m_plane_center - instance_offset; + cut_center_offset[Z] -= sla_shift_z; + + return translation_transform(cut_center_offset) * m_rotation_m; +} + +void update_object_cut_id(CutObjectBase& cut_id, ModelObjectCutAttributes attributes, const int dowels_count) +{ + // we don't save cut information, if result will not contains all parts of initial object + if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || + !attributes.has(ModelObjectCutAttribute::KeepLower) || + attributes.has(ModelObjectCutAttribute::InvalidateCutInfo)) + return; + + if (cut_id.id().invalid()) + cut_id.init(); + // increase check sum, if it's needed + { + int cut_obj_cnt = -1; + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::KeepLower)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) cut_obj_cnt+= dowels_count; + if (cut_obj_cnt > 0) + cut_id.increase_check_sum(size_t(cut_obj_cnt)); + } +} + +static void check_objects_after_cut(const ModelObjectPtrs& objects) +{ + std::vector err_objects_names; + for (const ModelObject* object : objects) { + std::vector connectors_names; + connectors_names.reserve(object->volumes.size()); + for (const ModelVolume* vol : object->volumes) + if (vol->cut_info.is_connector) + connectors_names.push_back(vol->name); + const size_t connectors_count = connectors_names.size(); + sort_remove_duplicates(connectors_names); + if (connectors_count != connectors_names.size()) + err_objects_names.push_back(object->name); + } + if (err_objects_names.empty()) + return; + + wxString names = from_u8(err_objects_names[0]); + for (size_t i = 1; i < err_objects_names.size(); i++) + names += ", " + from_u8(err_objects_names[i]); + WarningDialog(wxGetApp().plater(), format_wxstr("Objects(%1%) have duplicated connectors. " + "Some connectors may be missing in slicing result.\n" + "Please report to PrusaSlicer team in which scenario this issue happened.\n" + "Thank you.", names)).ShowModal(); +} + +void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id) +{ + for (ModelObject* obj : model.objects) + if (obj->is_cut() && obj->cut_id.has_same_id(cut_id) && !obj->cut_id.is_equal(cut_id)) + obj->cut_id.copy(cut_id); +} + +void GLGizmoCut3D::perform_cut(const Selection& selection) +{ + if (!can_perform_cut()) + return; + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + + wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); + + Plater* plater = wxGetApp().plater(); + ModelObject* mo = plater->model().objects[object_idx]; + if (!mo) + return; + + // deactivate CutGizmo and than perform a cut + m_parent.reset_all_gizmos(); + + // perform cut + { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Cut by Plane")); + + // This shall delete the part selection class and deallocate the memory. + ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); }); + + const bool cut_with_groove = CutMode(m_mode) == CutMode::cutTongueAndGroove; + const bool cut_by_contour = !cut_with_groove && m_part_selection.valid(); + + ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr; + if (cut_mo) + cut_mo->cut_connectors = mo->cut_connectors; + else + cut_mo = mo; + + int dowels_count = 0; + const bool has_connectors = !mo->cut_connectors.empty(); + // update connectors pos as offset of its center before cut performing + apply_connectors_in_model(cut_mo , dowels_count); + + wxBusyCursor wait; + + ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) | + only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | + only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | + only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | + only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) | + only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo); + + // update cut_id for the cut object in respect to the attributes + update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); + + Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes); + const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection.get_cut_parts(), dowels_count): + cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) : + cut.perform_with_plane(); + + check_objects_after_cut(new_objects); + + // save cut_id to post update synchronization + const CutObjectBase cut_id = cut_mo->cut_id; + + // update cut results on plater and in the model + plater->apply_cut_object_to_model(object_idx, new_objects); + + synchronize_model_after_cut(plater->model(), cut_id); + } +} + +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_contours/* = true*/) +{ + const float sla_shift = m_c->selection_info()->get_sla_shift(); + + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; + const Camera& camera = wxGetApp().plater()->get_camera(); + + // Calculate intersection with the clipping plane. + const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(true); + Vec3d point; + Vec3d direction; + Vec3d hit; + MeshRaycaster::line_from_mouse_pos(mouse_position, Transform3d::Identity(), camera, point, direction); + Vec3d normal = -cp->get_normal().cast(); + double den = normal.dot(direction); + if (den != 0.) { + double t = (-cp->get_offset() - normal.dot(point))/den; + hit = (point + t * direction); + } else + return false; + + // Now check if the hit is not obscured by a selected part on this side of the plane. + // FIXME: This would be better solved by remembering which contours are active. We will + // probably need that anyway because there is not other way to find out which contours + // to render. If you want to uncomment it, fix it first. It does not work yet. + /*for (size_t id = 0; id < m_part_selection.parts.size(); ++id) { + if (! m_part_selection.parts[id].selected) { + Vec3f pos, normal; + const ModelObject* model_object = m_part_selection.model_object; + const Vec3d volume_offset = m_part_selection.model_object->volumes[id]->get_offset(); + Transform3d tr = model_object->instances[m_part_selection.instance_idx]->get_matrix() * model_object->volumes[id]->get_matrix(); + if (m_part_selection.parts[id].raycaster.unproject_on_mesh(mouse_position, tr, camera, pos, normal)) + return false; + } + }*/ + + if (respect_contours) + { + // Do not react to clicks outside a contour (or inside a contour that is ignored) + int cont_id = m_c->object_clipper()->is_projection_inside_cut(hit); + if (cont_id == -1) + return false; + if (m_part_selection.valid()) { + const std::vector& ign = *m_part_selection.get_ignored_contours_ptr(); + if (std::find(ign.begin(), ign.end(), cont_id) != ign.end()) + return false; + } + } + + + // recalculate hit to object's local position + Vec3d hit_d = hit; + hit_d -= mi->get_offset(); + hit_d[Z] -= sla_shift; + + // Return both the point and the facet normal. + pos = hit_d; + pos_world = hit; + + return true; +} + +void GLGizmoCut3D::clear_selection() +{ + m_selected.clear(); + m_selected_count = 0; +} + +void GLGizmoCut3D::reset_connectors() +{ + m_c->selection_info()->model_object()->cut_connectors.clear(); + update_raycasters_for_picking(); + clear_selection(); + check_and_update_connectors_state(); +} + +void GLGizmoCut3D::init_connector_shapes() +{ + for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug, CutConnectorType::Snap}) + for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prism}) { + if (type == CutConnectorType::Dowel && style == CutConnectorStyle::Frustum) + continue; + for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { + if (type == CutConnectorType::Snap && shape != CutConnectorShape::Circle) + continue; + const CutConnectorAttributes attribs = { type, style, shape }; + indexed_triangle_set its = get_connector_mesh(attribs); + m_shapes[attribs].model.init_from(its); + m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } + } +} + +void GLGizmoCut3D::update_connector_shape() +{ + CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; + + if (m_connector_type == CutConnectorType::Snap) { + indexed_triangle_set its = get_connector_mesh(attribs); + m_shapes[attribs].reset(); + m_shapes[attribs].model.init_from(its); + m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + + //const indexed_triangle_set its = get_connector_mesh(attribs); + //m_connector_mesh.clear(); + //m_connector_mesh = TriangleMesh(its); + } + + +} + +bool GLGizmoCut3D::cut_line_processing() const +{ + return !m_line_beg.isApprox(Vec3d::Zero()); +} + +void GLGizmoCut3D::discard_cut_line_processing() +{ + m_line_beg = m_line_end = Vec3d::Zero(); +} + +bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position) +{ + const Camera& camera = wxGetApp().plater()->get_camera(); + + Vec3d pt; + Vec3d dir; + MeshRaycaster::line_from_mouse_pos(mouse_position, Transform3d::Identity(), camera, pt, dir); + dir.normalize(); + pt += dir; // Move the pt along dir so it is not clipped. + + if (action == SLAGizmoEventType::LeftDown && !cut_line_processing()) { + m_line_beg = pt; + m_line_end = pt; + on_unregister_raycasters_for_picking(); + return true; + } + + if (cut_line_processing()) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + m_groove_editing = true; + reset_cut_by_contours(); + + m_line_end = pt; + if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) { + Vec3d line_dir = m_line_end - m_line_beg; + if (line_dir.norm() < 3.0) + return true; + + Vec3d cross_dir = line_dir.cross(dir).normalized(); + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir).toRotationMatrix(); + + const Vec3d new_plane_center = m_bb_center + cross_dir * cross_dir.dot(pt - m_bb_center); + // update transformed bb + const auto new_tbb = transformed_bounding_box(new_plane_center, m); + const GLVolume* first_volume = m_parent.get_selection().get_first_volume(); + Vec3d instance_offset = first_volume->get_instance_offset(); + instance_offset[Z] += first_volume->get_sla_shift_z(); + + const Vec3d trans_center_pos = m.inverse() * (new_plane_center - instance_offset) + new_tbb.center(); + if (new_tbb.contains(trans_center_pos)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Cut by line"), UndoRedo::SnapshotType::GizmoAction); + m_transformed_bounding_box = new_tbb; + set_center(new_plane_center); + m_start_dragging_m = m_rotation_m = m; + m_ar_plane_center = m_plane_center; + } + + m_angle_arc.reset(); + discard_cut_line_processing(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + m_groove_editing = false; + reset_cut_by_contours(); + } + } + else if (action == SLAGizmoEventType::Moving) + this->set_dirty(); + return true; + } + return false; +} + +bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_position) +{ + if (!m_connectors_editing) + return false; + + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_position.cast(), pos, pos_world)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Add connector"), UndoRedo::SnapshotType::GizmoAction); + unselect_all_connectors(); + + connectors.emplace_back(pos, m_rotation_m, + m_connector_size * 0.5f, m_connector_depth_ratio, + m_connector_size_tolerance * 0.5f, m_connector_depth_ratio_tolerance, + m_connector_angle, + CutConnectorAttributes( CutConnectorType(m_connector_type), + CutConnectorStyle(m_connector_style), + CutConnectorShape(m_connector_shape_id))); + m_selected.push_back(true); + m_selected_count = 1; + assert(m_selected.size() == connectors.size()); + update_raycasters_for_picking(); + m_parent.set_as_dirty(); + check_and_update_connectors_state(); + + return true; + } + return false; +} + +bool GLGizmoCut3D::delete_selected_connectors(CutConnectors& connectors) +{ + if (connectors.empty()) + return false; + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Delete connector"), UndoRedo::SnapshotType::GizmoAction); + + // remove connectors + for (int i = int(connectors.size()) - 1; i >= 0; i--) + if (m_selected[i]) + connectors.erase(connectors.begin() + i); + // remove selections + m_selected.erase(std::remove_if(m_selected.begin(), m_selected.end(), [](const auto& selected) { + return selected; }), m_selected.end()); + m_selected_count = 0; + + assert(m_selected.size() == connectors.size()); + update_raycasters_for_picking(); + m_parent.set_as_dirty(); + check_and_update_connectors_state(); + return true; +} + +void GLGizmoCut3D::select_connector(int idx, bool select) +{ + m_selected[idx] = select; + if (select) + ++m_selected_count; + else + --m_selected_count; +} + +bool GLGizmoCut3D::is_selection_changed(bool alt_down, bool shift_down) +{ + if (m_hover_id >= m_connectors_group_id) { + if (alt_down) + select_connector(m_hover_id - m_connectors_group_id, false); + else { + if (!shift_down) + unselect_all_connectors(); + select_connector(m_hover_id - m_connectors_group_id, true); + } + return true; + } + return false; +} + +void GLGizmoCut3D::process_selection_rectangle(CutConnectors &connectors) +{ + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + + ModelObject* mo = m_c->selection_info()->model_object(); + int active_inst = m_c->selection_info()->get_active_instance(); + + // First collect positions of all the points in world coordinates. + Transformation trafo = mo->instances[active_inst]->get_transformation(); + trafo.set_offset(trafo.get_offset() + double(m_c->selection_info()->get_sla_shift()) * Vec3d::UnitZ()); + + std::vector points; + for (const CutConnector&connector : connectors) + points.push_back(connector.pos + trafo.get_offset()); + + // Now ask the rectangle which of the points are inside. + std::vector points_idxs = m_selection_rectangle.contains(points); + m_selection_rectangle.stop_dragging(); + + for (size_t idx : points_idxs) + select_connector(int(idx), rectangle_status == GLSelectionRectangle::EState::Select); +} + +bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (is_dragging() || m_connector_mode == CutConnectorMode::Auto) + return false; + + if ( (m_hover_id < 0 || m_hover_id == CutPlane) && shift_down && ! m_connectors_editing && + (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::Moving) ) + return process_cut_line(action, mouse_position); + + if (!m_keep_upper || !m_keep_lower) + return false; + + if (!m_connectors_editing) + return false; + + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + + if (action == SLAGizmoEventType::LeftDown) { + if (shift_down || alt_down) { + // left down with shift - show the selection rectangle: + if (m_hover_id == -1) + m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + } + else + // If there is no selection and no hovering, add new point + if (m_hover_id == -1 && !shift_down && !alt_down) + if (!add_connector(connectors, mouse_position)) + m_ldown_mouse_position = mouse_position; + return true; + } + + if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle.is_dragging()) { + if ((m_ldown_mouse_position - mouse_position).norm() < 5.) + unselect_all_connectors(); + return is_selection_changed(alt_down, shift_down); + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + process_selection_rectangle(connectors); + return true; + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::RightDown && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id < m_connectors_group_id) + return false; + unselect_all_connectors(); + select_connector(m_hover_id - m_connectors_group_id, true); + return delete_selected_connectors(connectors); + } + + if (action == SLAGizmoEventType::Delete) + return delete_selected_connectors(connectors); + + if (action == SLAGizmoEventType::SelectAll) { + select_all_connectors(); + return true; + } + + return false; +} + +CommonGizmosDataID GLGizmoCut3D::on_get_requirements() const { + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::ObjectClipper)); +} + +void GLGizmoCut3D::data_changed(bool is_serializing) +{ + update_bb(); + if (auto oc = m_c->object_clipper()) + oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); +} + + + + +indexed_triangle_set GLGizmoCut3D::get_connector_mesh(CutConnectorAttributes connector_attributes) +{ + indexed_triangle_set connector_mesh; + + int sectorCount{ 1 }; + switch (CutConnectorShape(connector_attributes.shape)) { + case CutConnectorShape::Triangle: + sectorCount = 3; + break; + case CutConnectorShape::Square: + sectorCount = 4; + break; + case CutConnectorShape::Circle: + sectorCount = 360; + break; + case CutConnectorShape::Hexagon: + sectorCount = 6; + break; + default: + break; + } + + if (connector_attributes.type == CutConnectorType::Snap) + connector_mesh = its_make_snap(1.0, 1.0, m_snap_space_proportion, m_snap_bulge_proportion); + else if (connector_attributes.style == CutConnectorStyle::Prism) + connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); + else if (connector_attributes.type == CutConnectorType::Plug) + connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount)); + else + connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); + + return connector_mesh; +} + +void GLGizmoCut3D::apply_cut_connectors(ModelObject* mo, const std::string& connector_name) +{ + if (mo->cut_connectors.empty()) + return; + + using namespace Geometry; + + size_t connector_id = mo->cut_id.connectors_cnt(); + for (const CutConnector& connector : mo->cut_connectors) { + TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); + // Mesh will be centered when loading. + ModelVolume* new_volume = mo->add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); + + // Transform the new modifier to be aligned inside the instance + new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m * + rotation_transform(-connector.z_angle * Vec3d::UnitZ()) * + scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); + + new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; + new_volume->name = connector_name + "-" + std::to_string(++connector_id); + } + mo->cut_id.increase_connectors_cnt(mo->cut_connectors.size()); + + // delete all connectors + mo->cut_connectors.clear(); +} + + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 0d745fe3cb..2c36dbd2a5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -1,73 +1,390 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoCut_hpp_ #define slic3r_GLGizmoCut_hpp_ #include "GLGizmoBase.hpp" +#include "slic3r/GUI/GLSelectionRectangle.hpp" #include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/I18N.hpp" #include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/CutUtils.hpp" +#include "imgui/imgui.h" namespace Slic3r { + +enum class CutConnectorType : int; +class ModelVolume; +class GLShaderProgram; +struct CutConnectorAttributes; + namespace GUI { +class Selection; -class GLGizmoCut : public GLGizmoBase +enum class SLAGizmoEventType : unsigned char; + +namespace CommonGizmosDataObjects { class ObjectClipper; } + +class GLGizmoCut3D : public GLGizmoBase { - static const double Offset; - static const double Margin; - static const std::array GrabberColor; - - double m_cut_z{ 0.0 }; - double m_max_z{ 0.0 }; - double m_start_z{ 0.0 }; - Vec3d m_drag_pos; - Vec3d m_drag_center; - bool m_keep_upper{ true }; - bool m_keep_lower{ true }; - bool m_rotate_lower{ false }; - // BBS: m_do_segment - bool m_cut_to_parts {false}; - bool m_do_segment{ false }; - double m_segment_smoothing_alpha{ 0.5 }; - int m_segment_number{ 5 }; - - struct CutContours - { - TriangleMesh mesh; - GLModel contours; - double cut_z{ 0.0 }; - Vec3d position{ Vec3d::Zero() }; - Vec3d shift{ Vec3d::Zero() }; - ObjectID object_id; - int instance_idx{ -1 }; + enum GrabberID { + X = 0, + Y, + Z, + CutPlane, + CutPlaneZRotation, + CutPlaneXMove, + CutPlaneYMove, + Count, }; - CutContours m_cut_contours; + Transform3d m_rotation_m{ Transform3d::Identity() }; + double m_snap_step{ 1.0 }; + int m_connectors_group_id; + + // archived values + Vec3d m_ar_plane_center { Vec3d::Zero() }; + Transform3d m_start_dragging_m{ Transform3d::Identity() }; + + Vec3d m_plane_center{ Vec3d::Zero() }; + // data to check position of the cut palne center on gizmo activation + Vec3d m_min_pos{ Vec3d::Zero() }; + Vec3d m_max_pos{ Vec3d::Zero() }; + Vec3d m_bb_center{ Vec3d::Zero() }; + Vec3d m_center_offset{ Vec3d::Zero() }; + + BoundingBoxf3 m_bounding_box; + BoundingBoxf3 m_transformed_bounding_box; + + // values from RotationGizmo + double m_radius{ 0.0 }; + double m_grabber_radius{ 0.0 }; + double m_grabber_connection_len{ 0.0 }; + Vec3d m_cut_plane_start_move_pos {Vec3d::Zero()}; + + double m_snap_coarse_in_radius{ 0.0 }; + double m_snap_coarse_out_radius{ 0.0 }; + double m_snap_fine_in_radius{ 0.0 }; + double m_snap_fine_out_radius{ 0.0 }; + + // dragging angel in hovered axes + double m_angle{ 0.0 }; + + TriangleMesh m_connector_mesh; + // workaround for using of the clipping plane normal + Vec3d m_clp_normal{ Vec3d::Ones() }; + + Vec3d m_line_beg{ Vec3d::Zero() }; + Vec3d m_line_end{ Vec3d::Zero() }; + + Vec2d m_ldown_mouse_position{ Vec2d::Zero() }; + + GLModel m_grabber_connection; + GLModel m_cut_line; + + PickingModel m_plane; + PickingModel m_sphere; + PickingModel m_cone; + PickingModel m_cube; + std::map m_shapes; + std::vector> m_raycasters; + + GLModel m_circle; + GLModel m_scale; + GLModel m_snap_radii; + GLModel m_reference_radius; + GLModel m_angle_arc; + + Vec3d m_old_center; + Vec3d m_cut_normal; + + struct InvalidConnectorsStatistics + { + unsigned int outside_cut_contour; + unsigned int outside_bb; + bool is_overlap; + + void invalidate() { + outside_cut_contour = 0; + outside_bb = 0; + is_overlap = false; + } + } m_info_stats; + + bool m_keep_upper{ true }; + bool m_keep_lower{ true }; + bool m_keep_as_parts{ false }; + bool m_place_on_cut_upper{ true }; + bool m_place_on_cut_lower{ false }; + bool m_rotate_upper{ false }; + bool m_rotate_lower{ false }; + + // Input params for cut with tongue and groove + Cut::Groove m_groove; + bool m_groove_editing { false }; + + bool m_is_slider_editing_done { false }; + + // Input params for cut with snaps + float m_snap_bulge_proportion{ 0.15f }; + float m_snap_space_proportion{ 0.3f }; + + bool m_hide_cut_plane{ false }; + bool m_connectors_editing{ false }; + bool m_cut_plane_as_circle{ false }; + + float m_connector_depth_ratio{ 3.f }; + float m_connector_size{ 2.5f }; + float m_connector_angle{ 0.f }; + + float m_connector_depth_ratio_tolerance{ 0.1f }; + float m_connector_size_tolerance{ 0.f }; + + float m_label_width{ 0.f }; + float m_control_width{ 200.f }; + double m_editing_window_width; + bool m_imperial_units{ false }; + + float m_contour_width{ 0.4f }; + float m_cut_plane_radius_koef{ 1.5f }; + + mutable std::vector m_selected; // which pins are currently selected + int m_selected_count{ 0 }; + + GLSelectionRectangle m_selection_rectangle; + + std::vector m_invalid_connectors_idxs; + bool m_was_cut_plane_dragged { false }; + bool m_was_contour_selected { false }; + + // Vertices of the groove used to detection if groove is valid + std::vector m_groove_vertices; + + class PartSelection { + public: + PartSelection() = default; + PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc); + PartSelection(const ModelObject* mo, int instance_idx_in); + ~PartSelection() { m_model.clear_objects(); } + + struct Part { + GLModel glmodel; + MeshRaycaster raycaster; + bool selected; + bool is_modifier; + }; + + void render(const Vec3d* normal, GLModel& sphere_model); + void toggle_selection(const Vec2d& mouse_pos); + void turn_over_selection(); + ModelObject* model_object() { return m_model.objects.front(); } + bool valid() const { return m_valid; } + bool is_one_object() const; + const std::vector& parts() const { return m_parts; } + const std::vector* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); } + + std::vector get_cut_parts(); + + private: + Model m_model; + int m_instance_idx; + std::vector m_parts; + bool m_valid = false; + std::vector, std::vector>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below + std::vector m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object) + + std::vector m_contour_points; // Debugging + std::vector> m_debug_pts; // Debugging + + void add_object(const ModelObject* object); + }; + + PartSelection m_part_selection; + + std::vector> m_shortcuts_cut; + std::vector> m_shortcuts_connector; + + enum class CutMode { + cutPlanar + , cutTongueAndGroove + //, cutGrig + //,cutRadial + //,cutModular + }; + + enum class CutConnectorMode { + Auto + , Manual + }; + + std::vector m_modes; + size_t m_mode{ size_t(CutMode::cutPlanar) }; + + std::vector m_connector_modes; + CutConnectorMode m_connector_mode{ CutConnectorMode::Manual }; + + std::vector m_connector_types; + CutConnectorType m_connector_type; + + std::vector m_connector_styles; + int m_connector_style; + + std::vector m_connector_shapes; + int m_connector_shape_id; + + std::vector m_axis_names; + + std::map m_part_orientation_names; + + std::map m_labels_map; public: - GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - - double get_cut_z() const { return m_cut_z; } - void set_cut_z(double cut_z); + GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; + bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_contours = true); + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + + bool is_in_editing_mode() const override { return m_connectors_editing; } + bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); } + bool is_looking_forward() const; + + /// + /// Drag of plane + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + void shift_cut(double delta); + void rotate_vec3d_around_plane_center(Vec3d&vec); + void put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset); + void update_clipper(); + void invalidate_cut_plane(); + + BoundingBoxf3 bounding_box() const; + BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, const Transform3d& rotation_m = Transform3d::Identity()) const; protected: - virtual bool on_init() override; - virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } - virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } - virtual std::string on_get_name() const override; - virtual void on_set_state() override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; - virtual void on_update(const UpdateData& data) override; - virtual void on_render() override; - virtual void on_render_for_picking() override; - virtual void on_render_input_window(float x, float y, float bottom_limit) override; + bool on_init() override; + void on_load(cereal::BinaryInputArchive&ar) override; + void on_save(cereal::BinaryOutputArchive&ar) const override; + std::string on_get_name() const override; + void on_set_state() override; + CommonGizmosDataID on_get_requirements() const override; + void on_set_hover_id() override; + bool on_is_activable() const override; + bool on_is_selectable() const override; + Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const; + void dragging_grabber_move(const GLGizmoBase::UpdateData &data); + void dragging_grabber_rotation(const GLGizmoBase::UpdateData &data); + void dragging_connector(const GLGizmoBase::UpdateData &data); + void on_dragging(const UpdateData&data) override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_render() override; + + void render_debug_input_window(float x); + void unselect_all_connectors(); + void select_all_connectors(); + void apply_selected_connectors(std::function apply_fn); + void render_connectors_input_window(CutConnectors &connectors, float x, float y, float bottom_limit); + void render_build_size(); + void reset_cut_plane(); + void set_connectors_editing(bool connectors_editing); + void flip_cut_plane(); + void process_contours(); + void reset_cut_by_contours(); + void render_flip_plane_button(bool disable_pred = false); + void add_vertical_scaled_interval(float interval); + void add_horizontal_scaled_interval(float interval); + void add_horizontal_shift(float shift); + void render_color_marker(float size, const ImU32& color); + void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance); + void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val); + bool render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val); + void render_snap_specific_input(const std::string& label, const wxString& tooltip, float& in_val, const float& init_val, const float min_val, const float max_val); + void render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit); + void init_input_window_data(CutConnectors &connectors); + void render_input_window_warning() const; + bool add_connector(CutConnectors&connectors, const Vec2d&mouse_position); + bool delete_selected_connectors(CutConnectors&connectors); + void select_connector(int idx, bool select); + bool is_selection_changed(bool alt_down, bool shift_down); + void process_selection_rectangle(CutConnectors &connectors); + + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; + void update_raycasters_for_picking(); + void set_volumes_picking_state(bool state); + void update_raycasters_for_picking_transform(); + + void update_plane_model(); + + void on_render_input_window(float x, float y, float bottom_limit) override; + void show_tooltip_information(float x, float y); + + bool wants_enter_leave_snapshots() const override { return true; } + std::string get_gizmo_entering_text() const override { return _u8L("Entering Cut gizmo"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); } + std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); } + + void data_changed(bool is_serializing) override; + Transform3d get_cut_matrix(const Selection& selection); private: - void perform_cut(const Selection& selection); - double calc_projection(const Linef3& mouse_ray) const; - BoundingBoxf3 bounding_box() const; - void update_contours(); + void set_center(const Vec3d¢er, bool update_tbb = false); + void switch_to_mode(size_t new_mode); + bool render_cut_mode_combo(); + bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx); + bool render_double_input(const std::string& label, double& value_in); + bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f); + void render_move_center_input(int axis); + void render_connect_mode_radio_button(CutConnectorMode mode); + bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; + bool render_connect_type_radio_button(CutConnectorType type); + bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); + bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); + void render_connectors(); + + bool can_perform_cut() const; + bool has_valid_groove() const; + bool has_valid_contour() const; + void apply_connectors_in_model(ModelObject* mo, int &dowels_count); + bool cut_line_processing() const; + void discard_cut_line_processing(); + + void apply_color_clip_plane_colors(); + void render_cut_plane(); + static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix); + void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width); + void render_rotation_snapping(GrabberID axis, const ColorRGBA& color); + void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef = 1.0); + void render_cut_plane_grabbers(); + void render_cut_line(); + void perform_cut(const Selection&selection); + void set_center_pos(const Vec3d¢er_pos, bool update_tbb = false); + void update_bb(); + void init_picking_models(); + void init_rendering_items(); + void render_clipper_cut(); + void clear_selection(); + void reset_connectors(); + void init_connector_shapes(); + void update_connector_shape(); + void validate_connector_settings(); + bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); + void check_and_update_connectors_state(); + + void toggle_model_objects_visibility(); + + indexed_triangle_set its_make_groove_plane(); + + indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); + void apply_cut_connectors(ModelObject* mo, const std::string& connector_name); }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.cpp index b96be246a3..90b274decd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.cpp @@ -32,9 +32,9 @@ std::string GLGizmoFaceDetector::on_get_name() const void GLGizmoFaceDetector::on_render() { - if (m_iva.has_VBOs()) { - ::glColor4f(0.f, 0.f, 1.f, 0.4f); - m_iva.render(); + if (model.is_initialized()) { + model.set_color({0.f, 0.f, 1.f, 0.4f}); + model.render(); } } @@ -72,7 +72,7 @@ void GLGizmoFaceDetector::on_render_input_window(float x, float y, float bottom_ void GLGizmoFaceDetector::on_set_state() { if (get_state() == On) { - m_iva.release_geometry(); + model.reset(); display_exterior_face(); } } @@ -94,7 +94,10 @@ void GLGizmoFaceDetector::perform_recognition(const Selection& selection) void GLGizmoFaceDetector::display_exterior_face() { int cnt = 0; - m_iva.release_geometry(); + model.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; const ModelObjectPtrs& objects = wxGetApp().model().objects; for (ModelObject* mo : objects) { @@ -110,19 +113,15 @@ void GLGizmoFaceDetector::display_exterior_face() continue; for (int i = 0; i < 3; ++i) { - m_iva.push_geometry(double(mv_its.vertices[facet_vert_idxs[i]](0)), - double(mv_its.vertices[facet_vert_idxs[i]](1)), - double(mv_its.vertices[facet_vert_idxs[i]](2)), - 0., 0., 1.); + init_data.add_vertex((Vec3f) mv_its.vertices[facet_vert_idxs[i]].cast(), Vec3f{0.0f, 0.0f, 1.0f}); } - m_iva.push_triangle(cnt, cnt + 1, cnt + 2); + init_data.add_uint_triangle(cnt, cnt + 1, cnt + 2); cnt += 3; } } } - - m_iva.finalize_geometry(true); + model.init_from(std::move(init_data)); } CommonGizmosDataID GLGizmoFaceDetector::on_get_requirements() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp index c20cf4c209..c028a3f2b2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp @@ -28,7 +28,7 @@ private: void perform_recognition(const Selection& selection); void display_exterior_face(); - GLIndexedVertexArray m_iva; + GUI::GLModel model; double m_sample_interval = {0.5}; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index c095c0915e..1a6ad83ca9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -105,7 +105,7 @@ bool GLGizmoFdmSupports::on_init() return true; } -void GLGizmoFdmSupports::render_painter_gizmo() const +void GLGizmoFdmSupports::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); @@ -116,8 +116,8 @@ void GLGizmoFdmSupports::render_painter_gizmo() const //BBS: draw support volumes if (m_volume_ready && m_support_volume && (m_edit_state != state_generating)) { - //m_support_volume->set_render_color(); - ::glColor4f(0.f, 0.7f, 0.f, 0.7f); + // TODO: FIXME + m_support_volume->set_render_color({0.f, 0.7f, 0.f, 0.7f}); m_support_volume->render(); } @@ -177,8 +177,12 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const if (is_left_handed) glsafe(::glFrontFace(GL_CW)); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg)); Matrix3f normal_matrix = static_cast(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast()); @@ -188,9 +192,8 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const shader->set_uniform("slope.actived", m_parent.is_using_slope()); shader->set_uniform("slope.volume_world_normal_matrix", normal_matrix); shader->set_uniform("slope.normal_z", normal_z); - m_triangle_selectors[mesh_id]->render(m_imgui); + m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); - glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); } @@ -427,7 +430,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -441,7 +444,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_drag_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position(clp_dist, true); + if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } ImGui::Separator(); @@ -640,7 +643,7 @@ void GLGizmoFdmSupports::update_from_model_object(bool first_update) m_volume_timestamps.clear(); int volume_id = -1; - std::vector> ebt_colors; + std::vector ebt_colors; ebt_colors.push_back(GLVolume::NEUTRAL_COLOR); ebt_colors.push_back(TriangleSelectorGUI::enforcers_color); ebt_colors.push_back(TriangleSelectorGUI::blockers_color); @@ -894,13 +897,16 @@ void GLGizmoFdmSupports::run_thread() print->set_status(100, L("Support Generated")); goto _finished; } + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; for (const SupportLayer *support_layer : m_print_instance.print_object->support_layers()) { for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) { - _3DScene::extrusionentity_to_verts(extrusion_entity, float(support_layer->print_z), m_print_instance.shift, *m_support_volume); + _3DScene::extrusionentity_to_verts(extrusion_entity, float(support_layer->print_z), m_print_instance.shift, init_data); } } + m_support_volume->model.init_from(std::move(init_data)); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished extrusionentity_to_verts, update status to 100%"; print->set_status(100, L("Support Generated")); @@ -926,7 +932,6 @@ _finished: void GLGizmoFdmSupports::generate_support_volume() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",before finalize_geometry"; - m_support_volume->indexed_vertex_array.finalize_geometry(m_parent.is_initialized()); std::unique_lock lck(m_mutex); m_volume_ready = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 6960a81dc6..306d1f13bb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -5,6 +5,7 @@ //BBS #include "libslic3r/Print.hpp" #include "libslic3r/ObjectID.hpp" +#include "slic3r/GUI/3DScene.hpp" #include @@ -15,7 +16,7 @@ class GLGizmoFdmSupports : public GLGizmoPainterBase public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void render_painter_gizmo() const override; + void render_painter_gizmo() override; //BBS: add edit state enum EditState { @@ -39,7 +40,7 @@ protected: std::string get_gizmo_entering_text() const override { return "Entering Paint-on supports"; } std::string get_gizmo_leaving_text() const override { return "Leaving Paint-on supports"; } - std::string get_action_snapshot_name() override { return "Paint-on supports editing"; } + std::string get_action_snapshot_name() const override { return "Paint-on supports editing"; } // BBS wchar_t m_current_tool = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 4ad8136bba..e4a4f895e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,6 +1,11 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoFlatten.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" @@ -16,14 +21,43 @@ namespace GUI { GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) - , m_normal(Vec3d::Zero()) - , m_starting_center(Vec3d::Zero()) +{} + +bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) { + if (mouse_event.LeftDown()) { + if (m_hover_id != -1) { + Selection &selection = m_parent.get_selection(); + if (selection.is_single_full_instance()) { + // Rotate the object so the normal points downward: + selection.flattening_rotate(m_planes[m_hover_id].normal); + m_parent.do_rotate(L("Gizmo-Place on Face")); + wxGetApp().obj_manipul()->set_dirty(); + } + return true; + } + } + else if (mouse_event.LeftUp()) + return m_hover_id != -1; + + return false; +} + +void GLGizmoFlatten::data_changed(bool is_serializing) +{ + const Selection & selection = m_parent.get_selection(); + const ModelObject *model_object = nullptr; + int instance_id = -1; + if (selection.is_single_full_instance() || + selection.is_from_single_object() ) { + model_object = selection.get_model()->objects[selection.get_object_idx()]; + instance_id = selection.get_instance_idx(); + } + set_flattening_data(model_object, instance_id); } bool GLGizmoFlatten::on_init() { - // BBS m_shortcut_key = WXK_CONTROL_F; return true; } @@ -49,77 +83,71 @@ bool GLGizmoFlatten::on_is_activable() const return m_parent.get_selection().is_single_full_instance(); } -void GLGizmoFlatten::on_start_dragging() -{ - if (m_hover_id != -1) { - assert(m_planes_valid); - m_normal = m_planes[m_hover_id].normal; - m_starting_center = m_parent.get_selection().get_bounding_box().center(); - } -} - void GLGizmoFlatten::on_render() { const Selection& selection = m_parent.get_selection(); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); if (selection.is_single_full_instance()) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); + const Transform3d& inst_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d model_matrix = Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * inst_matrix; + const Transform3d view_model_matrix = camera.get_view_matrix() * model_matrix; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (this->is_plane_update_necessary()) update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - if (i == m_hover_id) - glsafe(::glColor4fv(GLGizmoBase::FLATTEN_HOVER_COLOR.data())); - else - glsafe(::glColor4fv(GLGizmoBase::FLATTEN_COLOR.data())); - - if (m_planes[i].vbo.has_VBOs()) - m_planes[i].vbo.render(); + m_planes[i].vbo.model.set_color(i == m_hover_id ? GLGizmoBase::FLATTEN_HOVER_COLOR : GLGizmoBase::FLATTEN_COLOR); + m_planes[i].vbo.model.render(); } - glsafe(::glPopMatrix()); } glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); + shader->stop_using(); } -void GLGizmoFlatten::on_render_for_picking() +void GLGizmoFlatten::on_register_raycasters_for_picking() { - const Selection& selection = m_parent.get_selection(); + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_BLEND)); + assert(m_planes_casters.empty()); + + if (!m_planes.empty()) { + const Selection& selection = m_parent.get_selection(); + const Transform3d matrix = Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * + selection.get_first_volume()->get_instance_transformation().get_matrix(); - if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); - if (this->is_plane_update_necessary()) - update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - glsafe(::glColor4fv(picking_color_component(i).data())); - m_planes[i].vbo.render(); + m_planes_casters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_planes[i].vbo.mesh_raycaster, matrix)); } - glsafe(::glPopMatrix()); } - - glsafe(::glEnable(GL_CULL_FACE)); } -void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) +void GLGizmoFlatten::on_unregister_raycasters_for_picking() { - m_starting_center = Vec3d::Zero(); - if (model_object != m_old_model_object) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_parent.set_raycaster_gizmos_on_top(false); + m_planes_casters.clear(); +} + +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object, int instance_id) +{ + if (model_object != m_old_model_object || instance_id != m_old_instance_id) { m_planes.clear(); - m_planes_valid = false; + on_unregister_raycasters_for_picking(); } } @@ -136,6 +164,7 @@ void GLGizmoFlatten::update_planes() } ch = ch.convex_hull_3d(); m_planes.clear(); + on_unregister_raycasters_for_picking(); const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); // Following constants are used for discarding too small polygons. @@ -196,9 +225,7 @@ void GLGizmoFlatten::update_planes() } // Let's prepare transformation of the normal vector from mesh to instance coordinates. - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + const Matrix3d normal_matrix = inst_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); // Now we'll go through all the polygons, transform the points into xy plane to process them: for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { @@ -206,7 +233,7 @@ void GLGizmoFlatten::update_planes() const Vec3d& normal = m_planes[polygon_id].normal; // transform the normal according to the instance matrix: - Vec3d normal_transformed = t.get_matrix() * normal; + const Vec3d normal_transformed = normal_matrix * normal; // We are going to rotate about z and y to flatten the plane Eigen::Quaterniond q; @@ -219,7 +246,7 @@ void GLGizmoFlatten::update_planes() // And yes, it is a nasty thing to do. Whoever has time is free to refactor. Vec3d bb_size = BoundingBoxf3(polygon).size(); float sf = std::min(1./bb_size(0), 1./bb_size(1)); - Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); + Transform3d tr = Geometry::scale_transform({ sf, sf, 1.f }); polygon = transform(polygon, tr); polygon = Slic3r::Geometry::convex_hull(polygon); polygon = transform(polygon, tr.inverse()); @@ -324,34 +351,46 @@ void GLGizmoFlatten::update_planes() m_first_instance_scale = mo->instances.front()->get_scaling_factor(); m_first_instance_mirror = mo->instances.front()->get_mirror(); m_old_model_object = mo; + m_old_instance_id = m_c->selection_info()->get_active_instance(); // And finally create respective VBOs. The polygon is convex with // the vertices in order, so triangulation is trivial. for (auto& plane : m_planes) { - plane.vbo.reserve(plane.vertices.size()); - for (const auto& vert : plane.vertices) - plane.vbo.push_geometry(vert, plane.normal); - for (size_t i=1; i()); + } + for (size_t i = 1; i < plane.vertices.size() - 1; ++i) { + its.indices.emplace_back(0, i, i + 1); // triangle fan + } + + plane.vbo.model.init_from(its); + if (Geometry::Transformation(inst_matrix).is_left_handed()) { + // we need to swap face normals in case the object is mirrored + // for the raycaster to work properly + for (stl_triangle_vertex_indices& face : its.indices) { + if (its_face_normal(its, face).cast().dot(plane.normal) < 0.0) + std::swap(face[1], face[2]); + } + } + plane.vbo.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + // vertices are no more needed, clear memory + plane.vertices = std::vector(); } - m_planes_valid = true; + on_register_raycasters_for_picking(); } - bool GLGizmoFlatten::is_plane_update_necessary() const { const ModelObject* mo = m_c->selection_info()->model_object(); if (m_state != On || ! mo || mo->instances.empty()) return false; - if (! m_planes_valid || mo != m_old_model_object - || mo->volumes.size() != m_volumes_matrices.size()) + if (m_planes.empty() || mo != m_old_model_object + || mo->volumes.size() != m_volumes_matrices.size()) return true; // We want to recalculate when the scale changes - some planes could (dis)appear. @@ -367,13 +406,5 @@ bool GLGizmoFlatten::is_plane_update_necessary() const return false; } -Vec3d GLGizmoFlatten::get_flattening_normal() const -{ - Vec3d out = m_normal; - m_normal = Vec3d::Zero(); - m_starting_center = Vec3d::Zero(); - return out; -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index ab3c2c7bab..027480dbee 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -1,9 +1,13 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoFlatten_hpp_ #define slic3r_GLGizmoFlatten_hpp_ #include "GLGizmoBase.hpp" -#include "slic3r/GUI/3DScene.hpp" - +#include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/MeshUtils.hpp" namespace Slic3r { @@ -18,13 +22,13 @@ class GLGizmoFlatten : public GLGizmoBase // This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. private: - mutable Vec3d m_normal; struct PlaneData { std::vector vertices; // should be in fact local in update_planes() - GLIndexedVertexArray vbo; + PickingModel vbo; Vec3d normal; float area; + int picking_id{ -1 }; }; // This holds information to decide whether recalculation is necessary: @@ -34,10 +38,9 @@ private: Vec3d m_first_instance_mirror; std::vector m_planes; - bool m_planes_valid = false; - mutable Vec3d m_starting_center; + std::vector> m_planes_casters; const ModelObject* m_old_model_object = nullptr; - std::vector instances_matrices; + int m_old_instance_id{ -1 }; void update_planes(); bool is_plane_update_necessary() const; @@ -45,18 +48,25 @@ private: public: GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void set_flattening_data(const ModelObject* model_object); - Vec3d get_flattening_normal() const; + void set_flattening_data(const ModelObject* model_object, int instance_id); + + /// + /// Apply rotation on select plane + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + void data_changed(bool is_serializing) override; protected: - virtual bool on_init() override; - virtual std::string on_get_name() const override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; - virtual void on_render() override; - virtual void on_render_for_picking() override; - virtual void on_set_state() override; - virtual CommonGizmosDataID on_get_requirements() const override; + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_render() override; + void on_register_raycasters_for_picking() override; + void on_unregister_raycasters_for_picking() override; + void on_set_state() override; + CommonGizmosDataID on_get_requirements() const override; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 39bc98d7fb..03e12c22db 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -20,7 +20,6 @@ namespace GUI { GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { - m_vbo_cylinder.init_from(its_make_cylinder(1., 1.)); } @@ -63,6 +62,9 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) void GLGizmoHollow::on_render() { + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0)); + const Selection& selection = m_parent.get_selection(); const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); @@ -87,98 +89,75 @@ void GLGizmoHollow::on_render() glsafe(::glDisable(GL_BLEND)); } - -void GLGizmoHollow::on_render_for_picking() +void GLGizmoHollow::render_points(const Selection& selection, bool picking) { - const Selection& selection = m_parent.get_selection(); -//#if ENABLE_RENDER_PICKING_PASS -// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); -//#endif + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; - glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoHollow::render_points(const Selection& selection, bool picking) const -{ - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader) - shader->start_using(); - ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); + const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * vol->get_instance_transformation().get_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); - glsafe(::glMultMatrixd(instance_matrix.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d& projection_matrix = camera.get_projection_matrix(); - std::array render_color; + shader->set_uniform("projection_matrix", projection_matrix); + + ColorRGBA render_color; const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - size_t cache_size = drain_holes.size(); + const size_t cache_size = drain_holes.size(); for (size_t i = 0; i < cache_size; ++i) { const sla::DrainHole& drain_hole = drain_holes[i]; - const bool& point_selected = m_selected[i]; + const bool point_selected = m_selected[i]; if (is_mesh_point_clipped(drain_hole.pos.cast())) continue; // First decide about the color of the point. - if (picking) { - std::array color = picking_color_component(i); - render_color = color; - } + if (picking) + render_color = picking_color_component(i); else { - if (size_t(m_hover_id) == i) { - render_color = {0.f, 1.f, 1.f, 1.f}; - } + if (size_t(m_hover_id) == i) + render_color = ColorRGBA::CYAN(); else if (m_c->hollowed_mesh() && i < m_c->hollowed_mesh()->get_drainholes().size() && m_c->hollowed_mesh()->get_drainholes()[i].failed) { - render_color = {1.f, 0.f, 0.f, .5f}; - } - else { // neigher hover nor picking - - render_color[0] = point_selected ? 1.0f : 1.f; - render_color[1] = point_selected ? 0.3f : 1.f; - render_color[2] = point_selected ? 0.3f : 1.f; - render_color[3] = 0.5f; + render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; } + else // neither hover nor picking + render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); } - const_cast(&m_vbo_cylinder)->set_color(-1, render_color); + m_cylinder.set_color(render_color); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; if (vol->is_left_handed()) glFrontFace(GL_CW); // Matrices set, we can render the point mark now. Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - m_vbo_cylinder.render(); - glsafe(::glPopMatrix()); + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + const Eigen::AngleAxisd aa(q); + const Transform3d model_matrix = instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + m_cylinder.render(); if (vol->is_left_handed()) glFrontFace(GL_CCW); - glsafe(::glPopMatrix()); } - - glsafe(::glPopMatrix()); } - - bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const { if (m_c->object_clipper()->get_position() == 0.) @@ -544,14 +523,11 @@ RENDER_AGAIN: } m_imgui->disabled_begin(! m_enable_hollowing); - float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("offset")); ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); ImGui::PushItemWidth(window_width - settings_sliders_left); - m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm"); - if (m_imgui->get_last_slider_status().hovered) - m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width); + m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider @@ -561,9 +537,7 @@ RENDER_AGAIN: ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("quality")); ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f"); - if (m_imgui->get_last_slider_status().hovered) - m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width); + m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); slider_clicked |= m_imgui->get_last_slider_status().clicked; slider_edited |= m_imgui->get_last_slider_status().edited; @@ -574,9 +548,7 @@ RENDER_AGAIN: ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("closing_distance")); ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); - if (m_imgui->get_last_slider_status().hovered) - m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width); + m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip)); slider_clicked |= m_imgui->get_last_slider_status().clicked; slider_edited |= m_imgui->get_last_slider_status().edited; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 2cf08de2a0..e60ece0d52 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -40,15 +40,15 @@ private: bool on_init() override; void on_update(const UpdateData& data) override; void on_render() override; - void on_render_for_picking() override; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, bool picking = false); void hollow_mesh(bool postpone_error_messages = false); bool unsaved_changes() const; ObjectID m_old_mo_id = -1; - GLModel m_vbo_cylinder; + GLModel m_cylinder; + float m_new_hole_radius = 2.f; // Size of a new hole. float m_new_hole_height = 6.f; mutable std::vector m_selected; // which holes are currently selected diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp new file mode 100644 index 0000000000..0c8b42fe16 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -0,0 +1,2068 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "GLGizmoMeasure.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" + + +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/MeasureUtils.hpp" + +#include + +#include + +#include + +#include + +#include + +namespace Slic3r { +namespace GUI { + +static const Slic3r::ColorRGBA SELECTED_1ST_COLOR = { 0.25f, 0.75f, 0.75f, 1.0f }; +static const Slic3r::ColorRGBA SELECTED_2ND_COLOR = { 0.75f, 0.25f, 0.75f, 1.0f }; +static const Slic3r::ColorRGBA NEUTRAL_COLOR = {0.5f, 0.5f, 0.5f, 1.0f}; +static const Slic3r::ColorRGBA HOVER_COLOR = ColorRGBA::GREEN(); + +static const int POINT_ID = 100; +static const int EDGE_ID = 200; +static const int CIRCLE_ID = 300; +static const int PLANE_ID = 400; +static const int SEL_SPHERE_1_ID = 501; +static const int SEL_SPHERE_2_ID = 502; + +static const float TRIANGLE_BASE = 10.0f; +static const float TRIANGLE_HEIGHT = TRIANGLE_BASE * 1.618033f; + +static const std::string CTRL_STR = +#ifdef __APPLE__ +"⌘" +#else +"Ctrl" +#endif //__APPLE__ +; + +static std::string format_double(double value) +{ + char buf[1024]; + sprintf(buf, "%.3f", value); + return std::string(buf); +} + +static std::string format_vec3(const Vec3d& v) +{ + char buf[1024]; + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", v.x(), v.y(), v.z()); + return std::string(buf); +} + +static std::string surface_feature_type_as_string(Measure::SurfaceFeatureType type) +{ + switch (type) + { + default: + case Measure::SurfaceFeatureType::Undef: { return ("No feature"); } + case Measure::SurfaceFeatureType::Point: { return _u8L("Vertex"); } + case Measure::SurfaceFeatureType::Edge: { return _u8L("Edge"); } + case Measure::SurfaceFeatureType::Circle: { return _u8L("Circle"); } + case Measure::SurfaceFeatureType::Plane: { return _u8L("Plane"); } + } +} + +static std::string point_on_feature_type_as_string(Measure::SurfaceFeatureType type, int hover_id) +{ + std::string ret; + switch (type) { + case Measure::SurfaceFeatureType::Point: { ret = _u8L("Vertex"); break; } + case Measure::SurfaceFeatureType::Edge: { ret = _u8L("Point on edge"); break; } + case Measure::SurfaceFeatureType::Circle: { ret = _u8L("Point on circle"); break; } + case Measure::SurfaceFeatureType::Plane: { ret = _u8L("Point on plane"); break; } + default: { assert(false); break; } + } + return ret; +} + +static std::string center_on_feature_type_as_string(Measure::SurfaceFeatureType type) +{ + std::string ret; + switch (type) { + case Measure::SurfaceFeatureType::Edge: { ret = _u8L("Center of edge"); break; } + case Measure::SurfaceFeatureType::Circle: { ret = _u8L("Center of circle"); break; } + default: { assert(false); break; } + } + return ret; +} + +static GLModel::Geometry init_plane_data(const indexed_triangle_set& its, const std::vector& triangle_indices) +{ + GLModel::Geometry init_data; + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_indices(3 * triangle_indices.size()); + init_data.reserve_vertices(3 * triangle_indices.size()); + unsigned int i = 0; + for (int idx : triangle_indices) { + const Vec3f& v0 = its.vertices[its.indices[idx][0]]; + const Vec3f& v1 = its.vertices[its.indices[idx][1]]; + const Vec3f& v2 = its.vertices[its.indices[idx][2]]; + + const Vec3f n = (v1 - v0).cross(v2 - v0).normalized(); + init_data.add_vertex(v0, n); + init_data.add_vertex(v1, n); + init_data.add_vertex(v2, n); + init_data.add_triangle(i, i + 1, i + 2); + i += 3; + } + + return init_data; +} + +static GLModel::Geometry init_torus_data(unsigned int primary_resolution, unsigned int secondary_resolution, const Vec3f& center, + float radius, float thickness, const Vec3f& model_axis, const Transform3f& world_trafo) +{ + const unsigned int torus_sector_count = std::max(4, primary_resolution); + const unsigned int section_sector_count = std::max(4, secondary_resolution); + const float torus_sector_step = 2.0f * float(M_PI) / float(torus_sector_count); + const float section_sector_step = 2.0f * float(M_PI) / float(section_sector_count); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(torus_sector_count * section_sector_count); + data.reserve_indices(torus_sector_count * section_sector_count * 2 * 3); + + // vertices + const Transform3f local_to_world_matrix = world_trafo * Geometry::translation_transform(center.cast()).cast() * + Eigen::Quaternion::FromTwoVectors(Vec3f::UnitZ(), model_axis); + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const float section_angle = torus_sector_step * i; + const Vec3f radius_dir(std::cos(section_angle), std::sin(section_angle), 0.0f); + const Vec3f local_section_center = radius * radius_dir; + const Vec3f world_section_center = local_to_world_matrix * local_section_center; + const Vec3f local_section_normal = local_section_center.normalized().cross(Vec3f::UnitZ()).normalized(); + const Vec3f world_section_normal = (Vec3f)(local_to_world_matrix.matrix().block(0, 0, 3, 3) * local_section_normal).normalized(); + const Vec3f base_v = thickness * radius_dir; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const Vec3f v = Eigen::AngleAxisf(section_sector_step * j, world_section_normal) * base_v; + data.add_vertex(world_section_center + v, (Vec3f)v.normalized()); + } + } + + // triangles + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const unsigned int ii = i * section_sector_count; + const unsigned int ii_next = ((i + 1) % torus_sector_count) * section_sector_count; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const unsigned int j_next = (j + 1) % section_sector_count; + const unsigned int i0 = ii + j; + const unsigned int i1 = ii_next + j; + const unsigned int i2 = ii_next + j_next; + const unsigned int i3 = ii + j_next; + data.add_triangle(i0, i1, i2); + data.add_triangle(i0, i2, i3); + } + } + + return data; +} + +static bool is_feature_with_center(const Measure::SurfaceFeature& feature) +{ + const Measure::SurfaceFeatureType type = feature.get_type(); + return (type == Measure::SurfaceFeatureType::Circle || (type == Measure::SurfaceFeatureType::Edge && feature.get_extra_point().has_value())); +} + +static Vec3d get_feature_offset(const Measure::SurfaceFeature& feature) +{ + Vec3d ret; + switch (feature.get_type()) + { + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = feature.get_circle(); + ret = center; + break; + } + case Measure::SurfaceFeatureType::Edge: + { + std::optional p = feature.get_extra_point(); + assert(p.has_value()); + ret = *p; + break; + } + case Measure::SurfaceFeatureType::Point: + { + ret = feature.get_point(); + break; + } + default: { assert(false); } + } + + return ret; +} + +class TransformHelper +{ + struct Cache + { + std::array viewport; + Matrix4d ndc_to_ss_matrix; + Transform3d ndc_to_ss_matrix_inverse; + }; + + static Cache s_cache; + +public: + static Vec3d model_to_world(const Vec3d& model, const Transform3d& world_matrix) { + return world_matrix * model; + } + + static Vec4d world_to_clip(const Vec3d& world, const Matrix4d& projection_view_matrix) { + return projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0); + } + + static Vec3d clip_to_ndc(const Vec4d& clip) { + return Vec3d(clip.x(), clip.y(), clip.z()) / clip.w(); + } + + static Vec2d ndc_to_ss(const Vec3d& ndc, const std::array& viewport) { + const double half_w = 0.5 * double(viewport[2]); + const double half_h = 0.5 * double(viewport[3]); + return { half_w * ndc.x() + double(viewport[0]) + half_w, half_h * ndc.y() + double(viewport[1]) + half_h }; + }; + + static Vec4d model_to_clip(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix) { + return world_to_clip(model_to_world(model, world_matrix), projection_view_matrix); + } + + static Vec3d model_to_ndc(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix) { + return clip_to_ndc(world_to_clip(model_to_world(model, world_matrix), projection_view_matrix)); + } + + static Vec2d model_to_ss(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix, const std::array& viewport) { + return ndc_to_ss(clip_to_ndc(world_to_clip(model_to_world(model, world_matrix), projection_view_matrix)), viewport); + } + + static Vec2d world_to_ss(const Vec3d& world, const Matrix4d& projection_view_matrix, const std::array& viewport) { + return ndc_to_ss(clip_to_ndc(world_to_clip(world, projection_view_matrix)), viewport); + } + + static const Matrix4d& ndc_to_ss_matrix(const std::array& viewport) { + update(viewport); + return s_cache.ndc_to_ss_matrix; + } + + static const Transform3d ndc_to_ss_matrix_inverse(const std::array& viewport) { + update(viewport); + return s_cache.ndc_to_ss_matrix_inverse; + } + +private: + static void update(const std::array& viewport) { + if (s_cache.viewport == viewport) + return; + + const double half_w = 0.5 * double(viewport[2]); + const double half_h = 0.5 * double(viewport[3]); + s_cache.ndc_to_ss_matrix << half_w, 0.0, 0.0, double(viewport[0]) + half_w, + 0.0, half_h, 0.0, double(viewport[1]) + half_h, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0; + + s_cache.ndc_to_ss_matrix_inverse = s_cache.ndc_to_ss_matrix.inverse(); + s_cache.viewport = viewport; + } +}; + +TransformHelper::Cache TransformHelper::s_cache = { { 0, 0, 0, 0 }, Matrix4d::Identity(), Transform3d::Identity() }; + +GLGizmoMeasure::GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) +: GLGizmoBase(parent, icon_filename, sprite_id) +{ + GLModel::Geometry sphere_geometry = smooth_sphere(16, 7.5f); + m_sphere.mesh_raycaster = std::make_unique(std::make_shared(sphere_geometry.get_as_indexed_triangle_set())); + m_sphere.model.init_from(std::move(sphere_geometry)); + + GLModel::Geometry cylinder_geometry = smooth_cylinder(16, 5.0f, 1.0f); + m_cylinder.mesh_raycaster = std::make_unique(std::make_shared(cylinder_geometry.get_as_indexed_triangle_set())); + m_cylinder.model.init_from(std::move(cylinder_geometry)); +} + +bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) +{ + m_mouse_pos = { double(mouse_event.GetX()), double(mouse_event.GetY()) }; + + if (mouse_event.Moving()) { + // only for sure + m_mouse_left_down = false; + return false; + } + else if (mouse_event.Dragging()) { + // Enable/Disable panning/rotating the 3D scene + // Ctrl is pressed or the mouse is not hovering a selected volume + bool unlock_dragging = mouse_event.CmdDown() || (m_hover_id == -1 && !m_parent.get_selection().contains_volume(m_parent.get_first_hover_volume_idx())); + // mode is not center selection or mouse is not hovering a center + unlock_dragging &= !mouse_event.ShiftDown() || (m_hover_id != SEL_SPHERE_1_ID && m_hover_id != SEL_SPHERE_2_ID && m_hover_id != POINT_ID); + return !unlock_dragging; + } + else if (mouse_event.LeftDown()) { + // let the event pass through to allow panning/rotating the 3D scene + if (mouse_event.CmdDown()) + return false; + + if (m_hover_id != -1) { + m_mouse_left_down = true; + + auto detect_current_item = [this]() { + SelectedFeatures::Item item; + if (m_hover_id == SEL_SPHERE_1_ID) { + if (m_selected_features.first.is_center) + // mouse is hovering over a selected center + item = { true, m_selected_features.first.source, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.first.source)) } }; + else if (is_feature_with_center(*m_selected_features.first.feature)) + // mouse is hovering over a unselected center + item = { true, m_selected_features.first.feature, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.first.feature)) } }; + else + // mouse is hovering over a point + item = m_selected_features.first; + } + else if (m_hover_id == SEL_SPHERE_2_ID) { + if (m_selected_features.second.is_center) + // mouse is hovering over a selected center + item = { true, m_selected_features.second.source, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.second.source)) } }; + else if (is_feature_with_center(*m_selected_features.second.feature)) + // mouse is hovering over a center + item = { true, m_selected_features.second.feature, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.second.feature)) } }; + else + // mouse is hovering over a point + item = m_selected_features.second; + } + else { + switch (m_mode) + { + case EMode::FeatureSelection: { item = { false, m_curr_feature, m_curr_feature }; break; } + case EMode::PointSelection: { item = { false, m_curr_feature, Measure::SurfaceFeature(*m_curr_point_on_feature_position) }; break; } + } + } + return item; + }; + + auto requires_sphere_raycaster_for_picking = [this](const SelectedFeatures::Item& item) { + if (m_mode == EMode::PointSelection || item.feature->get_type() == Measure::SurfaceFeatureType::Point) + return true; + else if (m_mode == EMode::FeatureSelection) { + if (is_feature_with_center(*item.feature)) + return true; + } + return false; + }; + + if (m_selected_features.first.feature.has_value()) { + const SelectedFeatures::Item item = detect_current_item(); + if (m_selected_features.first != item) { + bool processed = false; + if (item.is_center) { + if (item.source == m_selected_features.first.feature) { + // switch 1st selection from feature to its center + m_selected_features.first = item; + processed = true; + } + else if (item.source == m_selected_features.second.feature) { + // switch 2nd selection from feature to its center + m_selected_features.second = item; + processed = true; + } + } + else if (is_feature_with_center(*item.feature)) { + if (m_selected_features.first.is_center && m_selected_features.first.source == item.feature) { + // switch 1st selection from center to its feature + m_selected_features.first = item; + processed = true; + } + else if (m_selected_features.second.is_center && m_selected_features.second.source == item.feature) { + // switch 2nd selection from center to its feature + m_selected_features.second = item; + processed = true; + } + } + + if (!processed) { + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + if (m_selected_features.second == item) + // 2nd feature deselection + m_selected_features.second.reset(); + else { + // 2nd feature selection + m_selected_features.second = item; + if (requires_sphere_raycaster_for_picking(item)) + m_selected_sphere_raycasters.push_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, SEL_SPHERE_2_ID, *m_sphere.mesh_raycaster)); + } + } + } + else { + remove_selected_sphere_raycaster(SEL_SPHERE_1_ID); + if (m_selected_features.second.feature.has_value()) { + // promote 2nd feature to 1st feature + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + m_selected_features.first = m_selected_features.second; + if (requires_sphere_raycaster_for_picking(m_selected_features.first)) + m_selected_sphere_raycasters.push_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, SEL_SPHERE_1_ID, *m_sphere.mesh_raycaster)); + m_selected_features.second.reset(); + } + else + // 1st feature deselection + m_selected_features.first.reset(); + } + } + else { + // 1st feature selection + const SelectedFeatures::Item item = detect_current_item(); + m_selected_features.first = item; + if (requires_sphere_raycaster_for_picking(item)) + m_selected_sphere_raycasters.push_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, SEL_SPHERE_1_ID, *m_sphere.mesh_raycaster)); + } + + update_measurement_result(); + + m_imgui->set_requires_extra_frame(); + + return true; + } + else + // if the mouse pointer is on any volume, filter out the event to prevent the user to move it + // equivalent tp: return (m_parent.get_first_hover_volume_idx() != -1); + return m_curr_feature.has_value(); + + // fix: prevent restart gizmo when reselect object + // take responsibility for left up + if (m_parent.get_first_hover_volume_idx() >= 0) + m_mouse_left_down = true; + } + else if (mouse_event.LeftUp()) { + if (m_mouse_left_down) { + // responsible for mouse left up after selecting plane + m_mouse_left_down = false; + return true; + } + if (m_hover_id == -1 && !m_parent.is_mouse_dragging()) + // avoid closing the gizmo if the user clicks outside of any volume + return true; + } + else if (mouse_event.RightDown()) { + // let the event pass through to allow panning/rotating the 3D scene + if (mouse_event.CmdDown()) + return false; + } + else if (mouse_event.Leaving()) + m_mouse_left_down = false; + + return false; +} + +void GLGizmoMeasure::data_changed(bool is_serializing) +{ + m_parent.toggle_sla_auxiliaries_visibility(false, nullptr, -1); + + update_if_needed(); + + m_last_inv_zoom = 0.0f; + m_last_plane_idx = -1; + if (m_pending_scale) { + update_measurement_result(); + m_pending_scale = false; + } + else + m_selected_features.reset(); + m_selected_sphere_raycasters.clear(); + m_editing_distance = false; + m_is_editing_distance_first_frame = true; +} + +bool GLGizmoMeasure::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (action == SLAGizmoEventType::ShiftDown) { + if (m_shift_kar_filter.is_first()) { + m_mode = EMode::PointSelection; + disable_scene_raycasters(); + } + m_shift_kar_filter.increase_count(); + } + else if (action == SLAGizmoEventType::ShiftUp) { + m_shift_kar_filter.reset_count(); + m_mode = EMode::FeatureSelection; + restore_scene_raycasters_state(); + } + else if (action == SLAGizmoEventType::Delete) { + m_selected_features.reset(); + m_selected_sphere_raycasters.clear(); + m_parent.request_extra_frame(); + } + else if (action == SLAGizmoEventType::Escape) { + if (!m_selected_features.first.feature.has_value()) { + update_measurement_result(); + return false; + } + else { + if (m_selected_features.second.feature.has_value()) { + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + m_selected_features.second.feature.reset(); + } + else { + remove_selected_sphere_raycaster(SEL_SPHERE_1_ID); + m_selected_features.first.feature.reset(); + } + + update_measurement_result(); + } + } + + return true; +} + +bool GLGizmoMeasure::on_init() +{ + m_shortcut_key = WXK_CONTROL_U; + + m_desc["feature_selection_caption"] = _L("ShiftLeft mouse button"); + m_desc["feature_selection"] = _L("Select feature"); + m_desc["point_selection_caption"] = _L("Shift + Left mouse button"); + m_desc["point_selection"] = _L("Select point"); + m_desc["reset_caption"] = _L("Delete"); + m_desc["reset"] = _L("Restart selection"); + m_desc["unselect_caption"] = _L("Esc"); + m_desc["unselect"] = _L("Unselect"); + + return true; +} + +void GLGizmoMeasure::on_set_state() +{ + if (m_state == Off) { + m_parent.toggle_sla_auxiliaries_visibility(true, nullptr, -1); + m_shift_kar_filter.reset_count(); + m_curr_feature.reset(); + m_curr_point_on_feature_position.reset(); + restore_scene_raycasters_state(); + m_editing_distance = false; + m_is_editing_distance_first_frame = true; + m_measuring.reset(); + m_raycaster.reset(); + } + else { + m_mode = EMode::FeatureSelection; + // store current state of scene raycaster for later use + m_scene_raycasters.clear(); + auto scene_raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + if (scene_raycasters != nullptr) { + m_scene_raycasters.reserve(scene_raycasters->size()); + for (auto r : *scene_raycasters) { + SceneRaycasterState state = { r, r->is_active() }; + m_scene_raycasters.emplace_back(state); + } + } + } +} + +std::string GLGizmoMeasure::on_get_name() const +{ + return _u8L("Measure"); +} + +bool GLGizmoMeasure::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + bool res = (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) ? + selection.is_single_full_instance() : + selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); + if (res) + res &= !selection.contains_sinking_volumes(); + + return res; +} + +void GLGizmoMeasure::on_render() +{ +#if ENABLE_MEASURE_GIZMO_DEBUG + render_debug_dialog(); +#endif // ENABLE_MEASURE_GIZMO_DEBUG + +// // do not render if the user is panning/rotating the 3d scene +// if (m_parent.is_mouse_dragging()) +// return; + + update_if_needed(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + const float inv_zoom = (float)camera.get_inv_zoom(); + + Vec3f position_on_model; + Vec3f normal_on_model; + size_t model_facet_idx; + const bool mouse_on_object = m_raycaster->unproject_on_mesh(m_mouse_pos, Transform3d::Identity(), camera, position_on_model, normal_on_model, nullptr, &model_facet_idx); + const bool is_hovering_on_feature = m_mode == EMode::PointSelection && m_hover_id != -1; + + auto update_circle = [this, inv_zoom]() { + if (m_last_inv_zoom != inv_zoom || m_last_circle != m_curr_feature) { + m_last_inv_zoom = inv_zoom; + m_last_circle = m_curr_feature; + m_circle.reset(); + const auto [center, radius, normal] = m_curr_feature->get_circle(); + GLModel::Geometry circle_geometry = init_torus_data(64, 16, center.cast(), float(radius), 5.0f * inv_zoom, normal.cast(), Transform3f::Identity()); + m_circle.mesh_raycaster = std::make_unique(std::make_shared(circle_geometry.get_as_indexed_triangle_set())); + m_circle.model.init_from(std::move(circle_geometry)); + return true; + } + return false; + }; + + if (m_mode == EMode::FeatureSelection || m_mode == EMode::PointSelection) { + if (m_hover_id == SEL_SPHERE_1_ID || m_hover_id == SEL_SPHERE_2_ID) { + // Skip feature detection if hovering on a selected point/center + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, POINT_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, EDGE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, PLANE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID); + m_curr_feature.reset(); + m_curr_point_on_feature_position.reset(); + } + else { + std::optional curr_feature = wxGetMouseState().LeftIsDown() ? m_curr_feature : + mouse_on_object ? m_measuring->get_feature(model_facet_idx, position_on_model.cast()) : std::nullopt; + + if (m_curr_feature != curr_feature || + (curr_feature.has_value() && curr_feature->get_type() == Measure::SurfaceFeatureType::Circle && (m_curr_feature != curr_feature || m_last_inv_zoom != inv_zoom))) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, POINT_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, EDGE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, PLANE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID); + m_raycasters.clear(); + m_curr_feature = curr_feature; + if (!m_curr_feature.has_value()) + return; + + switch (m_curr_feature->get_type()) { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + m_raycasters.insert({ POINT_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, POINT_ID, *m_sphere.mesh_raycaster) }); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + m_raycasters.insert({ EDGE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, EDGE_ID, *m_cylinder.mesh_raycaster) }); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + update_circle(); + m_raycasters.insert({ CIRCLE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID, *m_circle.mesh_raycaster) }); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + const auto [idx, normal, point] = m_curr_feature->get_plane(); + if (m_last_plane_idx != idx) { + m_last_plane_idx = idx; + const indexed_triangle_set& its = m_measuring->get_its(); + const std::vector& plane_triangles = m_measuring->get_plane_triangle_indices(idx); + GLModel::Geometry init_data = init_plane_data(its, plane_triangles); + m_plane.reset(); + m_plane.mesh_raycaster = std::make_unique(std::make_shared(init_data.get_as_indexed_triangle_set())); + } + + m_raycasters.insert({ PLANE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, PLANE_ID, *m_plane.mesh_raycaster) }); + break; + } + } + } + } + } + + if (m_mode != EMode::PointSelection) + m_curr_point_on_feature_position.reset(); + else if (is_hovering_on_feature) { + auto position_on_feature = [this](int feature_type_id, const Camera& camera, std::function callback = nullptr) -> Vec3d { + auto it = m_raycasters.find(feature_type_id); + if (it != m_raycasters.end() && it->second != nullptr) { + Vec3f p; + Vec3f n; + const Transform3d& trafo = it->second->get_transform(); + bool res = it->second->get_raycaster()->closest_hit(m_mouse_pos, trafo, camera, p, n); + if (res) { + if (callback) + p = callback(p); + return trafo * p.cast(); + } + } + return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + }; + + if (m_curr_feature.has_value()) { + switch (m_curr_feature->get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + m_curr_point_on_feature_position = m_curr_feature->get_point(); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const std::optional extra = m_curr_feature->get_extra_point(); + if (extra.has_value() && m_hover_id == POINT_ID) + m_curr_point_on_feature_position = *extra; + else { + const Vec3d pos = position_on_feature(EDGE_ID, camera, [](const Vec3f& v) { return Vec3f(0.0f, 0.0f, v.z()); }); + if (!pos.isApprox(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX))) + m_curr_point_on_feature_position = pos; + } + break; + } + case Measure::SurfaceFeatureType::Plane: + { + m_curr_point_on_feature_position = position_on_feature(PLANE_ID, camera); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = m_curr_feature->get_circle(); + if (m_hover_id == POINT_ID) + m_curr_point_on_feature_position = center; + else { + const Vec3d world_pof = position_on_feature(CIRCLE_ID, camera, [](const Vec3f& v) { return v; }); + const Eigen::Hyperplane plane(normal, center); + const Transform3d local_to_model_matrix = Geometry::translation_transform(center) * Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), normal); + const Vec3d local_proj = local_to_model_matrix.inverse() * plane.projection(world_pof); + double angle = std::atan2(local_proj.y(), local_proj.x()); + if (angle < 0.0) + angle += 2.0 * double(M_PI); + + const Vec3d local_pos = radius * Vec3d(std::cos(angle), std::sin(angle), 0.0); + m_curr_point_on_feature_position = local_to_model_matrix * local_pos; + } + break; + } + } + } + } + else { + m_curr_point_on_feature_position.reset(); + if (m_curr_feature.has_value() && m_curr_feature->get_type() == Measure::SurfaceFeatureType::Circle) { + if (update_circle()) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID); + auto it = m_raycasters.find(CIRCLE_ID); + if (it != m_raycasters.end()) + m_raycasters.erase(it); + m_raycasters.insert({ CIRCLE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID, *m_circle.mesh_raycaster) }); + } + } + } + + if (!m_curr_feature.has_value() && !m_selected_features.first.feature.has_value()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + const bool old_cullface = ::glIsEnabled(GL_CULL_FACE); + glsafe(::glDisable(GL_CULL_FACE)); + + const Transform3d& view_matrix = camera.get_view_matrix(); + + auto set_matrix_uniforms = [shader, &view_matrix](const Transform3d& model_matrix) { + const Transform3d view_model_matrix = view_matrix * model_matrix; + shader->set_uniform("view_model_matrix", view_model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + }; + + auto set_emission_uniform = [shader](const ColorRGBA& color, bool hover) { + shader->set_uniform("emission_factor", /*(color == GLVolume::SELECTED_COLOR) ? 0.0f :*/ + hover ? 0.5f : 0.25f); + }; + + auto render_feature = [this, set_matrix_uniforms, set_emission_uniform](const Measure::SurfaceFeature& feature, const std::vector& colors, + float inv_zoom, bool hover, bool update_raycasters_transform) { + switch (feature.get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + const Transform3d feature_matrix = Geometry::translation_transform(feature.get_point()) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(feature_matrix); + set_emission_uniform(colors.front(), hover); + m_sphere.model.set_color(colors.front()); + m_sphere.model.render(); + if (update_raycasters_transform) { + auto it = m_raycasters.find(POINT_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(feature_matrix); + } + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto& [center, radius, normal] = feature.get_circle(); + // render circle + const Transform3d circle_matrix = Transform3d::Identity(); + set_matrix_uniforms(circle_matrix); + if (update_raycasters_transform) { + set_emission_uniform(colors.front(), hover); + m_circle.model.set_color(colors.front()); + m_circle.model.render(); + auto it = m_raycasters.find(CIRCLE_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(circle_matrix); + } + else { + GLModel circle; + GLModel::Geometry circle_geometry = init_torus_data(64, 16, center.cast(), float(radius), 5.0f * inv_zoom, normal.cast(), Transform3f::Identity()); + circle.init_from(std::move(circle_geometry)); + set_emission_uniform(colors.front(), hover); + circle.set_color(colors.front()); + circle.render(); + } + // render center + if (colors.size() > 1) { + const Transform3d center_matrix = Geometry::translation_transform(center) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(center_matrix); + set_emission_uniform(colors.back(), hover); + m_sphere.model.set_color(colors.back()); + m_sphere.model.render(); + auto it = m_raycasters.find(POINT_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(center_matrix); + } + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const auto& [from, to] = feature.get_edge(); + // render edge + const Transform3d edge_matrix = Geometry::translation_transform(from) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), to - from) * + Geometry::scale_transform({ (double)inv_zoom, (double)inv_zoom, (to - from).norm() }); + set_matrix_uniforms(edge_matrix); + set_emission_uniform(colors.front(), hover); + m_cylinder.model.set_color(colors.front()); + m_cylinder.model.render(); + if (update_raycasters_transform) { + auto it = m_raycasters.find(EDGE_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(edge_matrix); + } + + // render extra point + if (colors.size() > 1) { + const std::optional extra = feature.get_extra_point(); + if (extra.has_value()) { + const Transform3d point_matrix = Geometry::translation_transform(*extra) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(point_matrix); + set_emission_uniform(colors.back(), hover); + m_sphere.model.set_color(colors.back()); + m_sphere.model.render(); + auto it = m_raycasters.find(POINT_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(point_matrix); + } + } + break; + } + case Measure::SurfaceFeatureType::Plane: + { + const auto& [idx, normal, pt] = feature.get_plane(); + assert(idx < m_plane_models_cache.size()); + set_matrix_uniforms(Transform3d::Identity()); + set_emission_uniform(colors.front(), hover); + m_plane_models_cache[idx].set_color(colors.front()); + m_plane_models_cache[idx].render(); + if (update_raycasters_transform) { + auto it = m_raycasters.find(PLANE_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(Transform3d::Identity()); + } + break; + } + } + }; + + auto hover_selection_color = [this]() { + return ((m_mode == EMode::PointSelection && !m_selected_features.first.feature.has_value()) || + (m_mode != EMode::PointSelection && (!m_selected_features.first.feature.has_value() || *m_curr_feature == *m_selected_features.first.feature))) ? + SELECTED_1ST_COLOR : SELECTED_2ND_COLOR; + }; + + auto hovering_color = [this, hover_selection_color]() { + return (m_mode == EMode::PointSelection) ? HOVER_COLOR : hover_selection_color(); + }; + + if (m_curr_feature.has_value()) { + // render hovered feature + + std::vector colors; + if (m_selected_features.first.feature.has_value() && *m_curr_feature == *m_selected_features.first.feature) { + // hovering over the 1st selected feature + if (m_selected_features.first.is_center) + // hovering over a center + colors = { NEUTRAL_COLOR, hovering_color() }; + else if (is_feature_with_center(*m_selected_features.first.feature)) + // hovering over a feature with center + colors = { hovering_color(), NEUTRAL_COLOR }; + else + colors = { hovering_color() }; + } + else if (m_selected_features.second.feature.has_value() && *m_curr_feature == *m_selected_features.second.feature) { + // hovering over the 2nd selected feature + if (m_selected_features.second.is_center) + // hovering over a center + colors = { NEUTRAL_COLOR, hovering_color() }; + else if (is_feature_with_center(*m_selected_features.second.feature)) + // hovering over a feature with center + colors = { hovering_color(), NEUTRAL_COLOR }; + else + colors = { hovering_color() }; + } + else { + switch (m_curr_feature->get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + colors.emplace_back(hover_selection_color()); + break; + } + case Measure::SurfaceFeatureType::Edge: + case Measure::SurfaceFeatureType::Circle: + { + if (m_selected_features.first.is_center && m_curr_feature == m_selected_features.first.source) + colors = { SELECTED_1ST_COLOR, NEUTRAL_COLOR }; + else if (m_selected_features.second.is_center && m_curr_feature == m_selected_features.second.source) + colors = { SELECTED_2ND_COLOR, NEUTRAL_COLOR }; + else + colors = { hovering_color(), hovering_color() }; + break; + } + case Measure::SurfaceFeatureType::Plane: + { + colors.emplace_back(hovering_color()); + break; + } + } + } + + render_feature(*m_curr_feature, colors, inv_zoom, true, true); + } + + if (m_selected_features.first.feature.has_value() && (!m_curr_feature.has_value() || *m_curr_feature != *m_selected_features.first.feature)) { + // render 1st selected feature + + std::optional feature_to_render; + std::vector colors; + bool requires_raycaster_update = false; + if (m_hover_id == SEL_SPHERE_1_ID && (m_selected_features.first.is_center || is_feature_with_center(*m_selected_features.first.feature))) { + // hovering over a center + feature_to_render = m_selected_features.first.source; + colors = { NEUTRAL_COLOR, SELECTED_1ST_COLOR }; + requires_raycaster_update = true; + } + else if (is_feature_with_center(*m_selected_features.first.feature)) { + // hovering over a feature with center + feature_to_render = m_selected_features.first.feature; + colors = { SELECTED_1ST_COLOR, NEUTRAL_COLOR }; + requires_raycaster_update = true; + } + else { + feature_to_render = m_selected_features.first.feature; + colors = { SELECTED_1ST_COLOR }; + requires_raycaster_update = m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Point; + } + + render_feature(*feature_to_render, colors, inv_zoom, m_hover_id == SEL_SPHERE_1_ID, false); + + if (requires_raycaster_update) { + auto it = std::find_if(m_selected_sphere_raycasters.begin(), m_selected_sphere_raycasters.end(), + [](std::shared_ptr item) { return SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, item->get_id()) == SEL_SPHERE_1_ID; }); + if (it != m_selected_sphere_raycasters.end()) + (*it)->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.first.feature)) * Geometry::scale_transform(inv_zoom)); + } + } + + if (m_selected_features.second.feature.has_value() && (!m_curr_feature.has_value() || *m_curr_feature != *m_selected_features.second.feature)) { + // render 2nd selected feature + + std::optional feature_to_render; + std::vector colors; + bool requires_raycaster_update = false; + if (m_hover_id == SEL_SPHERE_2_ID && (m_selected_features.second.is_center || is_feature_with_center(*m_selected_features.second.feature))) { + // hovering over a center + feature_to_render = m_selected_features.second.source; + colors = { NEUTRAL_COLOR, SELECTED_2ND_COLOR }; + requires_raycaster_update = true; + } + else if (is_feature_with_center(*m_selected_features.second.feature)) { + // hovering over a feature with center + feature_to_render = m_selected_features.second.feature; + colors = { SELECTED_2ND_COLOR, NEUTRAL_COLOR }; + requires_raycaster_update = true; + } + else { + feature_to_render = m_selected_features.second.feature; + colors = { SELECTED_2ND_COLOR }; + requires_raycaster_update = m_selected_features.second.feature->get_type() == Measure::SurfaceFeatureType::Point; + } + + render_feature(*feature_to_render, colors, inv_zoom, m_hover_id == SEL_SPHERE_2_ID, false); + + if (requires_raycaster_update) { + auto it = std::find_if(m_selected_sphere_raycasters.begin(), m_selected_sphere_raycasters.end(), + [](std::shared_ptr item) { return SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, item->get_id()) == SEL_SPHERE_2_ID; }); + if (it != m_selected_sphere_raycasters.end()) + (*it)->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.second.feature)) * Geometry::scale_transform(inv_zoom)); + } + } + + if (is_hovering_on_feature && m_curr_point_on_feature_position.has_value()) { + if (m_hover_id != POINT_ID) { + // render point on feature while SHIFT is pressed + const Transform3d matrix = Geometry::translation_transform(*m_curr_point_on_feature_position) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(matrix); + const ColorRGBA color = hover_selection_color(); + set_emission_uniform(color, true); + m_sphere.model.set_color(color); + m_sphere.model.render(); + } + } + + shader->stop_using(); + + if (old_cullface) + glsafe(::glEnable(GL_CULL_FACE)); + + render_dimensioning(); +} + +void GLGizmoMeasure::update_if_needed() +{ + auto update_plane_models_cache = [this](const indexed_triangle_set& its) { + m_plane_models_cache.clear(); + m_plane_models_cache.resize(m_measuring->get_num_of_planes(), GLModel()); + + auto& plane_models_cache = m_plane_models_cache; + const auto& measuring = m_measuring; + + //for (int idx = 0; idx < m_measuring->get_num_of_planes(); ++idx) { + tbb::parallel_for(tbb::blocked_range(0, m_measuring->get_num_of_planes()), + [&plane_models_cache, &measuring, &its](const tbb::blocked_range& range) { + for (size_t idx = range.begin(); idx != range.end(); ++idx) { + GLModel::Geometry init_data = init_plane_data(its, measuring->get_plane_triangle_indices(idx)); + plane_models_cache[idx].init_from(std::move(init_data)); + } + }); + }; + + auto do_update = [this, update_plane_models_cache](const std::vector& volumes_cache, const Selection& selection) { + TriangleMesh composite_mesh; + for (const auto& vol : volumes_cache) { +// if (selection.is_single_full_instance() && vol.volume->is_modifier()) +// continue; + + TriangleMesh volume_mesh = vol.volume->mesh(); + volume_mesh.transform(vol.world_trafo); + + if (vol.world_trafo.matrix().determinant() < 0.0) + volume_mesh.flip_triangles(); + + composite_mesh.merge(volume_mesh); + } + + m_measuring.reset(new Measure::Measuring(composite_mesh.its)); + update_plane_models_cache(m_measuring->get_its()); + m_raycaster.reset(new MeshRaycaster(std::make_shared(composite_mesh))); + m_volumes_cache = volumes_cache; + }; + + const Selection& selection = m_parent.get_selection(); + if (selection.is_empty()) + return; + + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + std::vector volumes_cache; + volumes_cache.reserve(idxs.size()); + for (unsigned int idx : idxs) { + const GLVolume* v = selection.get_volume(idx); + const int volume_idx = v->volume_idx(); + if (volume_idx < 0) + continue; + + const ModelObject* obj = selection.get_model()->objects[v->object_idx()]; + const ModelInstance* inst = obj->instances[v->instance_idx()]; + const ModelVolume* vol = obj->volumes[volume_idx]; + const VolumeCacheItem item = { + obj, inst, vol, + Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * inst->get_matrix() * vol->get_matrix() + }; + volumes_cache.emplace_back(item); + } + + if (m_state != On || volumes_cache.empty()) + return; + + if (m_measuring == nullptr || m_volumes_cache != volumes_cache) + do_update(volumes_cache, selection); +} + +void GLGizmoMeasure::disable_scene_raycasters() +{ + for (auto r : m_scene_raycasters) { + r.raycaster->set_active(false); + } +} + +void GLGizmoMeasure::restore_scene_raycasters_state() +{ + for (auto r : m_scene_raycasters) { + r.raycaster->set_active(r.state); + } +} + +void GLGizmoMeasure::render_dimensioning() +{ + static SelectedFeatures last_selected_features; + + if (!m_selected_features.first.feature.has_value()) + return; + + if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() != Measure::SurfaceFeatureType::Circle) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + auto point_point = [this, &shader](const Vec3d& v1, const Vec3d& v2, float distance) { + if ((v2 - v1).squaredNorm() < 0.000001 || distance < 0.001f) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + + // screen coordinates + const Vec2d v1ss = TransformHelper::world_to_ss(v1, projection_view_matrix, viewport); + const Vec2d v2ss = TransformHelper::world_to_ss(v2, projection_view_matrix, viewport); + + if (v1ss.isApprox(v2ss)) + return; + + const Vec2d v12ss = v2ss - v1ss; + const double v12ss_len = v12ss.norm(); + + const bool overlap = v12ss_len - 2.0 * TRIANGLE_HEIGHT < 0.0; + + const auto q12ss = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(v12ss.x(), v12ss.y(), 0.0)); + const auto q21ss = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(-v12ss.x(), -v12ss.y(), 0.0)); + + shader->set_uniform("projection_matrix", Transform3d::Identity()); + + const Vec3d v1ss_3 = { v1ss.x(), v1ss.y(), 0.0 }; + const Vec3d v2ss_3 = { v2ss.x(), v2ss.y(), 0.0 }; + + const Transform3d ss_to_ndc_matrix = TransformHelper::ndc_to_ss_matrix_inverse(viewport); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("dashed_thick_lines"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 1.0f); + shader->set_uniform("gap_size", 0.0f); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + + // stem + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss * Geometry::translation_transform(-2.0 * TRIANGLE_HEIGHT * Vec3d::UnitX()) * Geometry::scale_transform({ v12ss_len + 4.0 * TRIANGLE_HEIGHT, 1.0f, 1.0f }) : + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss * Geometry::scale_transform({ v12ss_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::WHITE()); + m_dimensioning.line.render(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(1.0f)); + + // arrow 1 + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss : + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q21ss); + m_dimensioning.triangle.render(); + + // arrow 2 + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v2ss_3) * q21ss : + ss_to_ndc_matrix * Geometry::translation_transform(v2ss_3) * q12ss); + m_dimensioning.triangle.render(); + + const bool use_inches = wxGetApp().app_config->get_bool("use_inches"); + const double curr_value = use_inches ? GizmoObjectManipulation::mm_to_in * distance : distance; + const std::string curr_value_str = format_double(curr_value); + const std::string units = use_inches ? _u8L("in") : _u8L("mm"); + const float value_str_width = 20.0f + ImGui::CalcTextSize(curr_value_str.c_str()).x; + static double edit_value = 0.0; + + ImGuiWrapper::push_common_window_style(m_parent.get_scale()); + const Vec2d label_position = 0.5 * (v1ss + v2ss); + m_imgui->set_next_window_pos(label_position.x(), viewport[3] - label_position.y(), ImGuiCond_Always, 0.0f, 1.0f); + m_imgui->set_next_window_bg_alpha(0.0f); + + if (!m_editing_distance) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 1.0f, 1.0f }); + m_imgui->begin(std::string("distance"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::AlignTextToFramePadding(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const std::string txt = curr_value_str + " " + units; + ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); + const ImGuiStyle& style = ImGui::GetStyle(); + draw_list->AddRectFilled({ pos.x - style.FramePadding.x, pos.y + style.FramePadding.y }, { pos.x + txt_size.x + 2.0f * style.FramePadding.x , pos.y + txt_size.y + 2.0f * style.FramePadding.y }, + ImGuiWrapper::to_ImU32(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f))); + ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); + m_imgui->text(txt); + ImGui::SameLine(); + if (m_imgui->image_button(ImGui::SliderFloatEditBtnIcon, _L("Edit to scale"))) { + m_editing_distance = true; + edit_value = curr_value; + m_imgui->requires_extra_frame(); + } + m_imgui->end(); + ImGui::PopStyleVar(3); + } + + if (m_editing_distance && !ImGui::IsPopupOpen("distance_popup")) + ImGui::OpenPopup("distance_popup"); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 1.0f, 1.0f }); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 4.0f, 0.0f }); + if (ImGui::BeginPopupModal("distance_popup", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration)) { + auto perform_scale = [this](double new_value, double old_value) { + if (new_value == old_value || new_value <= 0.0) + return; + + const double ratio = new_value / old_value; + wxGetApp().plater()->take_snapshot(_u8L("Scale")); + + struct TrafoData + { + double ratio; + Vec3d old_pivot; + Vec3d new_pivot; + Transform3d scale_matrix; + + TrafoData(double ratio, const Vec3d& old_pivot, const Vec3d& new_pivot) { + this->ratio = ratio; + this->scale_matrix = Geometry::scale_transform(ratio); + this->old_pivot = old_pivot; + this->new_pivot = new_pivot; + } + + Vec3d transform(const Vec3d& point) const { return this->scale_matrix * (point - this->old_pivot) + this->new_pivot; } + }; + + auto scale_feature = [](Measure::SurfaceFeature& feature, const TrafoData& trafo_data) { + switch (feature.get_type()) + { + case Measure::SurfaceFeatureType::Point: + { + feature = Measure::SurfaceFeature(trafo_data.transform(feature.get_point())); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const auto [from, to] = feature.get_edge(); + const std::optional extra = feature.get_extra_point(); + const std::optional new_extra = extra.has_value() ? trafo_data.transform(*extra) : extra; + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, trafo_data.transform(from), trafo_data.transform(to), new_extra); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = feature.get_circle(); + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Circle, trafo_data.transform(center), normal, std::nullopt, trafo_data.ratio * radius); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + const auto [idx, normal, origin] = feature.get_plane(); + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Plane, normal, trafo_data.transform(origin), std::nullopt, idx); + break; + } + default: { break; } + } + }; + + // apply scale + TransformationType type; + type.set_world(); + type.set_relative(); + type.set_joint(); + + // scale selection + Selection& selection = m_parent.get_selection(); + const Vec3d old_center = selection.get_bounding_box().center(); + selection.setup_cache(); + selection.scale(ratio * Vec3d::Ones(), type); + wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot + wxGetApp().obj_manipul()->set_dirty(); + + // scale dimensioning + const Vec3d new_center = selection.get_bounding_box().center(); + const TrafoData trafo_data(ratio, old_center, new_center); + scale_feature(*m_selected_features.first.feature, trafo_data); + if (m_selected_features.second.feature.has_value()) + scale_feature(*m_selected_features.second.feature, trafo_data); + + // update measure on next call to data_changed() + m_pending_scale = true; + }; + auto action_exit = [this]() { + m_editing_distance = false; + m_is_editing_distance_first_frame = true; + ImGui::CloseCurrentPopup(); + }; + auto action_scale = [perform_scale, action_exit](double new_value, double old_value) { + perform_scale(new_value, old_value); + action_exit(); + }; + + m_imgui->disable_background_fadeout_animation(); + ImGui::PushItemWidth(value_str_width); + if (ImGui::InputDouble("##distance", &edit_value, 0.0f, 0.0f, "%.3f")) { + } + + // trick to auto-select text in the input widgets on 1st frame + if (m_is_editing_distance_first_frame) { + ImGui::SetKeyboardFocusHere(0); + m_is_editing_distance_first_frame = false; + m_imgui->set_requires_extra_frame(); + } + + // handle keys input + if (ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) + action_scale(edit_value, curr_value); + else if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) + action_exit(); + + ImGui::SameLine(); + ImGuiWrapper::push_confirm_button_style(); + if (m_imgui->button(_CTX(L_CONTEXT("Scale", "Verb"), "Verb"))) + action_scale(edit_value, curr_value); + ImGuiWrapper::pop_confirm_button_style(); + ImGui::SameLine(); + ImGuiWrapper::push_cancel_button_style(); + if (m_imgui->button(_L("Cancel"))) + action_exit(); + ImGuiWrapper::pop_cancel_button_style(); + ImGui::EndPopup(); + } + ImGui::PopStyleVar(4); + ImGuiWrapper::pop_common_window_style(); + }; + + auto point_edge = [this, shader](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Point && f2.get_type() == Measure::SurfaceFeatureType::Edge); + std::pair e = f2.get_edge(); + const Vec3d v_proj = m_measurement_result.distance_infinite->to; + const Vec3d e1e2 = e.second - e.first; + const Vec3d v_proje1 = v_proj - e.first; + const bool on_e1_side = v_proje1.dot(e1e2) < -EPSILON; + const bool on_e2_side = !on_e1_side && v_proje1.norm() > e1e2.norm(); + if (on_e1_side || on_e2_side) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + const Transform3d ss_to_ndc_matrix = TransformHelper::ndc_to_ss_matrix_inverse(viewport); + + const Vec2d v_projss = TransformHelper::world_to_ss(v_proj, projection_view_matrix, viewport); + auto render_extension = [this, &v_projss, &projection_view_matrix, &viewport, &ss_to_ndc_matrix, shader](const Vec3d& p) { + const Vec2d pss = TransformHelper::world_to_ss(p, projection_view_matrix, viewport); + if (!pss.isApprox(v_projss)) { + const Vec2d pv_projss = v_projss - pss; + const double pv_projss_len = pv_projss.norm(); + + const auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(pv_projss.x(), pv_projss.y(), 0.0)); + + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("view_model_matrix", ss_to_ndc_matrix * Geometry::translation_transform({ pss.x(), pss.y(), 0.0 }) * q * + Geometry::scale_transform({ pv_projss_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::LIGHT_GRAY()); + m_dimensioning.line.render(); + } + }; + + render_extension(on_e1_side ? e.first : e.second); + } + }; + + auto arc_edge_edge = [this, &shader](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2, double radius = 0.0) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Edge && f2.get_type() == Measure::SurfaceFeatureType::Edge); + if (!m_measurement_result.angle.has_value()) + return; + + const double angle = m_measurement_result.angle->angle; + const Vec3d center = m_measurement_result.angle->center; + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + const bool coplanar = m_measurement_result.angle->coplanar; + + if (std::abs(angle) < EPSILON || std::abs(calc_radius) < EPSILON) + return; + + const double draw_radius = (radius > 0.0) ? radius : calc_radius; + + const Vec3d e1_unit = Measure::edge_direction(e1); + const Vec3d e2_unit = Measure::edge_direction(e2); + + const unsigned int resolution = std::max(2, 64 * angle / double(PI)); + const double step = angle / double(resolution); + const Vec3d normal = e1_unit.cross(e2_unit).normalized(); + + if (!m_dimensioning.arc.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(resolution + 1); + init_data.reserve_indices(resolution + 1); + + // vertices + indices + for (unsigned int i = 0; i <= resolution; ++i) { + const double a = step * double(i); + const Vec3d v = draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(a, normal)) * e1_unit); + init_data.add_vertex((Vec3f)v.cast()); + init_data.add_index(i); + } + + m_dimensioning.arc.init_from(std::move(init_data)); + } + + const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("dashed_thick_lines"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 1.0f); + shader->set_uniform("gap_size", 0.0f); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + + // arc + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center)); + m_dimensioning.arc.render(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(1.0f)); + + // arrows + auto render_arrow = [this, shader, &camera, &normal, ¢er, &e1_unit, draw_radius, step, resolution](unsigned int endpoint_id) { + const double angle = (endpoint_id == 1) ? 0.0 : step * double(resolution); + const Vec3d position_model = Geometry::translation_transform(center) * (draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(angle, normal)) * e1_unit)); + const Vec3d direction_model = (endpoint_id == 1) ? -normal.cross(position_model - center).normalized() : normal.cross(position_model - center).normalized(); + const auto qz = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), (endpoint_id == 1) ? normal : -normal); + const auto qx = Eigen::Quaternion::FromTwoVectors(qz * Vec3d::UnitX(), direction_model); + const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(position_model) * + qx * qz * Geometry::scale_transform(camera.get_inv_zoom()); + shader->set_uniform("view_model_matrix", view_model_matrix); + m_dimensioning.triangle.render(); + }; + + glsafe(::glDisable(GL_CULL_FACE)); + render_arrow(1); + render_arrow(2); + glsafe(::glEnable(GL_CULL_FACE)); + + // edge 1 extension + const Vec3d e11e12 = e1.second - e1.first; + const Vec3d e11center = center - e1.first; + const double e11center_len = e11center.norm(); + if (e11center_len > EPSILON && e11center.dot(e11e12) < 0.0) { + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Measure::edge_direction(e1.first, e1.second)) * + Geometry::scale_transform({ e11center_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::LIGHT_GRAY()); + m_dimensioning.line.render(); + } + + // edge 2 extension + const Vec3d e21center = center - e2.first; + const double e21center_len = e21center.norm(); + if (e21center_len > EPSILON) { + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Measure::edge_direction(e2.first, e2.second)) * + Geometry::scale_transform({ (coplanar && radius > 0.0) ? e21center_len : draw_radius, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::LIGHT_GRAY()); + m_dimensioning.line.render(); + } + + // label + // label world coordinates + const Vec3d label_position_world = Geometry::translation_transform(center) * (draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(step * 0.5 * double(resolution), normal)) * e1_unit)); + + // label screen coordinates + const std::array& viewport = camera.get_viewport(); + const Vec2d label_position_ss = TransformHelper::world_to_ss(label_position_world, + camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(), viewport); + + ImGuiWrapper::push_common_window_style(m_parent.get_scale()); + m_imgui->set_next_window_pos(label_position_ss.x(), viewport[3] - label_position_ss.y(), ImGuiCond_Always, 0.0f, 1.0f); + m_imgui->set_next_window_bg_alpha(0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + m_imgui->begin(wxString("##angle"), ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::AlignTextToFramePadding(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const std::string txt = format_double(Geometry::rad2deg(angle)) + "°"; + ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); + const ImGuiStyle& style = ImGui::GetStyle(); + draw_list->AddRectFilled({ pos.x - style.FramePadding.x, pos.y + style.FramePadding.y }, { pos.x + txt_size.x + 2.0f * style.FramePadding.x , pos.y + txt_size.y + 2.0f * style.FramePadding.y }, + ImGuiWrapper::to_ImU32(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f))); + ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); + m_imgui->text(txt); + m_imgui->end(); + ImGui::PopStyleVar(); + ImGuiWrapper::pop_common_window_style(); + }; + + auto arc_edge_plane = [this, arc_edge_edge](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Edge && f2.get_type() == Measure::SurfaceFeatureType::Plane); + if (!m_measurement_result.angle.has_value()) + return; + + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + + if (calc_radius == 0.0) + return; + + arc_edge_edge(Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e1.first, e1.second), + Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e2.first, e2.second), calc_radius); + }; + + auto arc_plane_plane = [this, arc_edge_edge](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Plane && f2.get_type() == Measure::SurfaceFeatureType::Plane); + if (!m_measurement_result.angle.has_value()) + return; + + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + + if (calc_radius == 0.0) + return; + + arc_edge_edge(Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e1.first, e1.second), + Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e2.first, e2.second), calc_radius); + }; + + shader->start_using(); + + if (!m_dimensioning.line.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(1.0f, 0.0f, 0.0f)); + + // indices + init_data.add_line(0, 1); + + m_dimensioning.line.init_from(std::move(init_data)); + } + + if (!m_dimensioning.triangle.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(3); + init_data.reserve_indices(3); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(-TRIANGLE_HEIGHT, 0.5f * TRIANGLE_BASE, 0.0f)); + init_data.add_vertex(Vec3f(-TRIANGLE_HEIGHT, -0.5f * TRIANGLE_BASE, 0.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + + m_dimensioning.triangle.init_from(std::move(init_data)); + } + + if (last_selected_features != m_selected_features) + m_dimensioning.arc.reset(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + const bool has_distance = m_measurement_result.has_distance_data(); + + const Measure::SurfaceFeature* f1 = &(*m_selected_features.first.feature); + const Measure::SurfaceFeature* f2 = nullptr; + std::unique_ptr temp_feature; + if (m_selected_features.second.feature.has_value()) + f2 = &(*m_selected_features.second.feature); + else { + assert(m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle); + temp_feature = std::make_unique(std::get<0>(m_selected_features.first.feature->get_circle())); + f2 = temp_feature.get(); + } + + if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() != Measure::SurfaceFeatureType::Circle) + return; + + Measure::SurfaceFeatureType ft1 = f1->get_type(); + Measure::SurfaceFeatureType ft2 = f2->get_type(); + + // Order features by type so following conditions are simple. + if (ft1 > ft2) { + std::swap(ft1, ft2); + std::swap(f1, f2); + } + + // If there is an angle to show, draw the arc: + if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Edge) + arc_edge_edge(*f1, *f2); + else if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Plane) + arc_edge_plane(*f1, *f2); + else if (ft1 == Measure::SurfaceFeatureType::Plane && ft2 == Measure::SurfaceFeatureType::Plane) + arc_plane_plane(*f1, *f2); + + if (has_distance){ + // Where needed, draw the extension of the edge to where the dist is measured: + if (ft1 == Measure::SurfaceFeatureType::Point && ft2 == Measure::SurfaceFeatureType::Edge) + point_edge(*f1, *f2); + + // Render the arrow between the points that the backend passed: + const Measure::DistAndPoints& dap = m_measurement_result.distance_infinite.has_value() + ? *m_measurement_result.distance_infinite + : *m_measurement_result.distance_strict; + point_point(dap.from, dap.to, dap.dist); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + + shader->stop_using(); +} + +static void add_row_to_table(std::function col_1 = nullptr, std::function col_2 = nullptr) +{ + assert(col_1 != nullptr && col_2 != nullptr); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + col_1(); + ImGui::TableSetColumnIndex(1); + col_2(); +} + +static void add_strings_row_to_table(ImGuiWrapper& imgui, const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) +{ + add_row_to_table([&]() { imgui.text_colored(col_1_color, col_1); }, [&]() { imgui.text_colored(col_2_color, col_2); }); +}; + +#if ENABLE_MEASURE_GIZMO_DEBUG +void GLGizmoMeasure::render_debug_dialog() +{ + auto add_feature_data = [this](const SelectedFeatures::Item& item) { + const std::string text = (item.source == item.feature) ? surface_feature_type_as_string(item.feature->get_type()) : point_on_feature_type_as_string(item.source->get_type(), m_hover_id); + add_strings_row_to_table(*m_imgui, "Type", ImGuiWrapper::COL_ORCA, text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + switch (item.feature->get_type()) + { + case Measure::SurfaceFeatureType::Point: + { + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORCA, format_vec3(item.feature->get_point()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + auto [from, to] = item.feature->get_edge(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORCA, format_vec3(from), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_ORCA, format_vec3(to), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + auto [idx, normal, origin] = item.feature->get_plane(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORCA, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_ORCA, format_vec3(origin), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_ORCA, format_double(idx), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + auto [center, radius, normal] = item.feature->get_circle(); + const Vec3d on_circle = center + radius * Measure::get_orthogonal(normal, true); + radius = (on_circle - center).norm(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORCA, format_vec3(center), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_ORCA, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_ORCA, format_double(radius), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + } + std::optional extra_point = item.feature->get_extra_point(); + if (extra_point.has_value()) + add_strings_row_to_table(*m_imgui, "m_pt3", ImGuiWrapper::COL_ORCA, format_vec3(*extra_point), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + }; + + m_imgui->begin("Measure tool debug", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + if (ImGui::BeginTable("Mode", 2)) { + std::string txt; + switch (m_mode) + { + case EMode::FeatureSelection: { txt = "Feature selection"; break; } + case EMode::PointSelection: { txt = "Point selection"; break; } + default: { assert(false); break; } + } + add_strings_row_to_table(*m_imgui, "Mode", ImGuiWrapper::COL_ORCA, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + + ImGui::Separator(); + if (ImGui::BeginTable("Hover", 2)) { + add_strings_row_to_table(*m_imgui, "Hover id", ImGuiWrapper::COL_ORCA, std::to_string(m_hover_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + const std::string txt = m_curr_feature.has_value() ? surface_feature_type_as_string(m_curr_feature->get_type()) : "None"; + add_strings_row_to_table(*m_imgui, "Current feature", ImGuiWrapper::COL_ORCA, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + + ImGui::Separator(); + if (!m_selected_features.first.feature.has_value() && !m_selected_features.second.feature.has_value()) + m_imgui->text("Empty selection"); + else { + const ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersH; + if (m_selected_features.first.feature.has_value()) { + m_imgui->text_colored(ImGuiWrapper::COL_ORCA, "Selection 1"); + if (ImGui::BeginTable("Selection 1", 2, flags)) { + add_feature_data(m_selected_features.first); + ImGui::EndTable(); + } + } + if (m_selected_features.second.feature.has_value()) { + m_imgui->text_colored(ImGuiWrapper::COL_ORCA, "Selection 2"); + if (ImGui::BeginTable("Selection 2", 2, flags)) { + add_feature_data(m_selected_features.second); + ImGui::EndTable(); + } + } + } + m_imgui->end(); +} +#endif // ENABLE_MEASURE_GIZMO_DEBUG + +void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit) +{ + static std::optional last_feature; + static EMode last_mode = EMode::FeatureSelection; + static SelectedFeatures last_selected_features; + + static float last_y = 0.0f; + static float last_h = 0.0f; + + if (m_editing_distance) + return; + + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + // Orca + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + + GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + + float caption_max = 0.f; + float total_text_max = 0.f; + for (const auto &t : std::array{"feature_selection", "point_selection", "reset", "unselect"}) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); + } + + const bool use_inches = wxGetApp().app_config->get_bool("use_inches"); + const std::string units = use_inches ? " " + _u8L("in") : " " + _u8L("mm"); + + // Show selection + { + auto format_item_text = [this, use_inches, &units](const SelectedFeatures::Item& item) { + if (!item.feature.has_value()) + return _u8L("None"); + + std::string text = (item.source == item.feature) ? surface_feature_type_as_string(item.feature->get_type()) : + item.is_center ? center_on_feature_type_as_string(item.source->get_type()) : point_on_feature_type_as_string(item.source->get_type(), m_hover_id); + if (item.feature.has_value() && item.feature->get_type() == Measure::SurfaceFeatureType::Circle) { + auto [center, radius, normal] = item.feature->get_circle(); + const Vec3d on_circle = center + radius * Measure::get_orthogonal(normal, true); + radius = (on_circle - center).norm(); + if (use_inches) + radius = GizmoObjectManipulation::mm_to_in * radius; + text += " (" + _u8L("Diameter") + ": " + format_double(2.0 * radius) + units + ")"; + } + else if (item.feature.has_value() && item.feature->get_type() == Measure::SurfaceFeatureType::Edge) { + auto [start, end] = item.feature->get_edge(); + double length = (end - start).norm(); + if (use_inches) + length = GizmoObjectManipulation::mm_to_in * length; + text += " (" + _u8L("Length") + ": " + format_double(length) + units + ")"; + } + return text; + }; + + const float selection_cap_length = ImGui::CalcTextSize((_u8L("Selection") + " 1").c_str()).x * 2; + + ImGui::AlignTextToFramePadding(); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(SELECTED_1ST_COLOR)); + m_imgui->text(_u8L("Selection") + " 1"); + ImGui::SameLine(selection_cap_length); + m_imgui->text(format_item_text(m_selected_features.first)); + ImGui::PopStyleColor(); + + ImGui::AlignTextToFramePadding(); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(SELECTED_2ND_COLOR)); + m_imgui->text(_u8L("Selection") + " 2"); + ImGui::SameLine(selection_cap_length); + m_imgui->text(format_item_text(m_selected_features.second)); + ImGui::PopStyleColor(); + } + + m_imgui->disabled_begin(!m_selected_features.first.feature.has_value()); + if (m_imgui->button(_L("Restart selection"))) { + m_selected_features.reset(); + m_selected_sphere_raycasters.clear(); + m_imgui->set_requires_extra_frame(); + } + m_imgui->disabled_end(); + + auto add_measure_row_to_table = [this](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + m_imgui->text_colored(col_1_color, col_1); + ImGui::TableSetColumnIndex(1); + m_imgui->text_colored(col_2_color, col_2); + ImGui::TableSetColumnIndex(2); + if (m_imgui->image_button(m_is_dark_mode ? ImGui::ClipboardBtnDarkIcon : ImGui::ClipboardBtnIcon, _L("Copy to clipboard"))) { + wxTheClipboard->Open(); + wxTheClipboard->SetData(new wxTextDataObject(wxString((col_1 + ": " + col_2).c_str(), wxConvUTF8))); + wxTheClipboard->Close(); + } + }; + + ImGui::Separator(); + m_imgui->text(_u8L("Measure")); + + const unsigned int max_measure_row_count = 2; + unsigned int measure_row_count = 0; + if (ImGui::BeginTable("Measure", 4)) { + if (m_selected_features.second.feature.has_value()) { + const Measure::MeasurementResult& measure = m_measurement_result; + if (measure.angle.has_value()) { + ImGui::PushID("ClipboardAngle"); + add_measure_row_to_table(_u8L("Angle"), ImGuiWrapper::COL_ORCA, format_double(Geometry::rad2deg(measure.angle->angle)) + "°", + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + + const bool show_strict = measure.distance_strict.has_value() && + (!measure.distance_infinite.has_value() || std::abs(measure.distance_strict->dist - measure.distance_infinite->dist) > EPSILON); + + if (measure.distance_infinite.has_value()) { + double distance = measure.distance_infinite->dist; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceInfinite"); + add_measure_row_to_table(show_strict ? _u8L("Perpendicular distance") : _u8L("Distance"), ImGuiWrapper::COL_ORCA, format_double(distance) + units, + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + if (show_strict) { + double distance = measure.distance_strict->dist; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceStrict"); + add_measure_row_to_table(_u8L("Direct distance"), ImGuiWrapper::COL_ORCA, format_double(distance) + units, + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + if (measure.distance_xyz.has_value() && measure.distance_xyz->norm() > EPSILON) { + Vec3d distance = *measure.distance_xyz; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceXYZ"); + add_measure_row_to_table(_u8L("Distance XYZ"), ImGuiWrapper::COL_ORCA, format_vec3(distance), + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + } + + // add dummy rows to keep dialog size fixed + for (unsigned int i = measure_row_count; i < max_measure_row_count; ++i) { + add_strings_row_to_table(*m_imgui, " ", ImGuiWrapper::COL_ORCA, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); + } + ImGui::EndTable(); + } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + show_tooltip_information(caption_max, x, get_cur_y); + + float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + + ImGui::PopStyleVar(2); + + if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { + // the dialog may have changed its size, ask for an extra frame to render it properly + last_feature = m_curr_feature; + last_mode = m_mode; + last_selected_features = m_selected_features; + m_imgui->set_requires_extra_frame(); + } + + GizmoImguiEnd(); + + // Orca + ImGuiWrapper::pop_toolbar_style(); +} + +void GLGizmoMeasure::on_register_raycasters_for_picking() +{ + // the features are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); +} + +void GLGizmoMeasure::on_unregister_raycasters_for_picking() +{ + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_parent.set_raycaster_gizmos_on_top(false); + m_raycasters.clear(); + m_selected_sphere_raycasters.clear(); +} + +void GLGizmoMeasure::remove_selected_sphere_raycaster(int id) +{ + auto it = std::find_if(m_selected_sphere_raycasters.begin(), m_selected_sphere_raycasters.end(), + [id](std::shared_ptr item) { return SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, item->get_id()) == id; }); + if (it != m_selected_sphere_raycasters.end()) + m_selected_sphere_raycasters.erase(it); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, id); +} + +void GLGizmoMeasure::update_measurement_result() +{ + if (!m_selected_features.first.feature.has_value()) + m_measurement_result = Measure::MeasurementResult(); + else if (m_selected_features.second.feature.has_value()) + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature, m_measuring.get()); + else if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle) + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, Measure::SurfaceFeature(std::get<0>(m_selected_features.first.feature->get_circle())), m_measuring.get()); +} + +void GLGizmoMeasure::show_tooltip_information(float caption_max, float x, float y) +{ + ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += m_imgui->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, ImGui::GetStyle().FramePadding.y }); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { + m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto &t : std::array{"feature_selection", "point_selection", "reset", "unselect"}) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp new file mode 100644 index 0000000000..0fc7577b43 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -0,0 +1,198 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_GLGizmoMeasure_hpp_ +#define slic3r_GLGizmoMeasure_hpp_ + +#include "GLGizmoBase.hpp" +#include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/GUI_Utils.hpp" +#include "slic3r/GUI/MeshUtils.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "libslic3r/Measure.hpp" +#include "libslic3r/Model.hpp" + +namespace Slic3r { + +enum class ModelVolumeType : int; + +namespace Measure { class Measuring; } + + +namespace GUI { + +enum class SLAGizmoEventType : unsigned char; + +class GLGizmoMeasure : public GLGizmoBase +{ + enum class EMode : unsigned char + { + FeatureSelection, + PointSelection + }; + + struct SelectedFeatures + { + struct Item + { + bool is_center{ false }; + std::optional source; + std::optional feature; + + bool operator == (const Item& other) const { + return this->is_center == other.is_center && this->source == other.source && this->feature == other.feature; + } + + bool operator != (const Item& other) const { + return !operator == (other); + } + + void reset() { + is_center = false; + source.reset(); + feature.reset(); + } + }; + + Item first; + Item second; + + void reset() { + first.reset(); + second.reset(); + } + + bool operator == (const SelectedFeatures & other) const { + if (this->first != other.first) return false; + return this->second == other.second; + } + + bool operator != (const SelectedFeatures & other) const { + return !operator == (other); + } + }; + + struct VolumeCacheItem + { + const ModelObject* object{ nullptr }; + const ModelInstance* instance{ nullptr }; + const ModelVolume* volume{ nullptr }; + Transform3d world_trafo; + + bool operator == (const VolumeCacheItem& other) const { + return this->object == other.object && this->instance == other.instance && this->volume == other.volume && + this->world_trafo.isApprox(other.world_trafo); + } + }; + + std::vector m_volumes_cache; + + EMode m_mode{ EMode::FeatureSelection }; + Measure::MeasurementResult m_measurement_result; + + std::unique_ptr m_measuring; // PIMPL + + PickingModel m_sphere; + PickingModel m_cylinder; + PickingModel m_circle; + PickingModel m_plane; + struct Dimensioning + { + GLModel line; + GLModel triangle; + GLModel arc; + }; + Dimensioning m_dimensioning; + + // Uses a standalone raycaster and not the shared one because of the + // difference in how the mesh is updated + std::unique_ptr m_raycaster; + + std::vector m_plane_models_cache; + std::map> m_raycasters; + // used to keep the raycasters for point/center spheres + std::vector> m_selected_sphere_raycasters; + std::optional m_curr_feature; + std::optional m_curr_point_on_feature_position; + struct SceneRaycasterState + { + std::shared_ptr raycaster{ nullptr }; + bool state{true}; + + }; + std::vector m_scene_raycasters; + + // These hold information to decide whether recalculation is necessary: + float m_last_inv_zoom{ 0.0f }; + std::optional m_last_circle; + int m_last_plane_idx{ -1 }; + + bool m_mouse_left_down{ false }; // for detection left_up of this gizmo + + Vec2d m_mouse_pos{ Vec2d::Zero() }; + + KeyAutoRepeatFilter m_shift_kar_filter; + + SelectedFeatures m_selected_features; + bool m_pending_scale{ false }; + bool m_editing_distance{ false }; + bool m_is_editing_distance_first_frame{ true }; + + void update_if_needed(); + + void disable_scene_raycasters(); + void restore_scene_raycasters_state(); + + void render_dimensioning(); + +#if ENABLE_MEASURE_GIZMO_DEBUG + void render_debug_dialog(); +#endif // ENABLE_MEASURE_GIZMO_DEBUG + +public: + GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + + /// + /// Apply rotation on select plane + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + void data_changed(bool is_serializing) override; + + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + + bool wants_enter_leave_snapshots() const override { return true; } + std::string get_gizmo_entering_text() const override { return _u8L("Entering Measure gizmo"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Measure gizmo"); } + std::string get_action_snapshot_name() const override { return _u8L("Measure gizmo editing"); } + +protected: + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_render() override; + void on_set_state() override; + + virtual void on_render_input_window(float x, float y, float bottom_limit) override; + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; + + void remove_selected_sphere_raycaster(int id); + void update_measurement_result(); + + // Orca + void show_tooltip_information(float caption_max, float x, float y); + +private: + // This map holds all translated description texts, so they can be easily referenced during layout calculations + // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. + std::map m_desc; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMeasure_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp index 20107305b7..fb19ff6459 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp @@ -50,7 +50,7 @@ bool GLGizmoMeshBoolean::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { - MeshRaycaster mesh_raycaster = MeshRaycaster(mo->volumes[mesh_id]->mesh()); + MeshRaycaster mesh_raycaster = MeshRaycaster(mo->volumes[mesh_id]->mesh_ptr()); if (mesh_raycaster.unproject_on_mesh(mouse_position, trafo_matrices[mesh_id], camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), &facet)) { // Is this hit the closest to the camera so far? @@ -84,6 +84,27 @@ bool GLGizmoMeshBoolean::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; } +bool GLGizmoMeshBoolean::on_mouse(const wxMouseEvent &mouse_event) +{ + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + // when control is down we allow scene pan and rotation even when clicking + // over some object + bool control_down = mouse_event.CmdDown(); + bool grabber_contains_mouse = (get_hover_id() != -1); + if (mouse_event.LeftDown()) { + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + // the gizmo got the event and took some action, there is no need + // to do anything more + return true; + } + + return false; +} + bool GLGizmoMeshBoolean::on_init() { m_shortcut_key = WXK_CONTROL_B; @@ -131,8 +152,8 @@ void GLGizmoMeshBoolean::on_render() } } - float src_color[3] = { 1.0f, 1.0f, 1.0f }; - float tool_color[3] = { 0.0f, 150.0f / 255.0f, 136.0f / 255.0f }; + ColorRGB src_color = { 1.0f, 1.0f, 1.0f }; + ColorRGB tool_color = {0.0f, 150.0f / 255.0f, 136.0f / 255.0f}; m_parent.get_selection().render_bounding_box(src_bb, src_color, m_parent.get_scale()); m_parent.get_selection().render_bounding_box(tool_bb, tool_color, m_parent.get_scale()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp index 42fa97eede..c012a68dca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp @@ -55,14 +55,22 @@ public: m_src.reset(); } - bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + bool gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down); + + /// + /// Implement when want to process mouse events in gizmo + /// Click, Right click, move, drag, ... + /// + /// Keep information about mouse click + /// Return True when use the information and don't want to + /// propagate it otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; protected: virtual bool on_init() override; virtual std::string on_get_name() const override; virtual bool on_is_activable() const override; virtual void on_render() override; - virtual void on_render_for_picking() override {} virtual void on_set_state() override; virtual CommonGizmosDataID on_get_requirements() const override; virtual void on_render_input_window(float x, float y, float bottom_limit); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 517e880d93..d9a396ba10 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -1,3 +1,8 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, David Kocík @kocikdav, Vojtěch Bubník @bubnikv +///|/ Copyright (c) 2021 Justin Schuh @jschuh +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" @@ -58,11 +63,11 @@ bool GLGizmoMmuSegmentation::on_is_activable() const } //BBS: use the global one in 3DScene.cpp -/*static std::vector> get_extruders_colors() +/*static std::vector get_extruders_colors() { unsigned char rgb_color[3] = {}; std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - std::vector> colors_out(colors.size()); + std::vector colors_out(colors.size()); for (const std::string &color : colors) { Slic3r::GUI::BitmapCache::parse_color(color, rgb_color); size_t color_idx = &color - &colors.front(); @@ -146,7 +151,7 @@ GLGizmoMmuSegmentation::GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::st { } -void GLGizmoMmuSegmentation::render_painter_gizmo() const +void GLGizmoMmuSegmentation::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); @@ -162,11 +167,10 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const glsafe(::glDisable(GL_BLEND)); } -void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) +void GLGizmoMmuSegmentation::data_changed(bool is_serializing) { - GLGizmoPainterBase::set_painter_gizmo_data(selection); - - if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().filaments_cnt() <= 1) + GLGizmoPainterBase::data_changed(is_serializing); + if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().extruders_edited_cnt() <= 1) return; ModelObject* model_object = m_c->selection_info()->model_object(); @@ -192,7 +196,7 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const { ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data(); - auto *shader = wxGetApp().get_shader("mm_gouraud"); + auto* shader = wxGetApp().get_shader("mm_gouraud"); if (!shader) return; shader->start_using(); @@ -221,18 +225,21 @@ void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix()* mv->get_matrix(); } - bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + const bool is_left_handed = trafo_matrix.matrix().determinant() < 0.0; if (is_left_handed) glsafe(::glFrontFace(GL_CW)); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); shader->set_uniform("volume_world_matrix", trafo_matrix); shader->set_uniform("volume_mirrored", is_left_handed); - m_triangle_selectors[mesh_id]->render(m_imgui); + m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); - glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); } @@ -276,18 +283,14 @@ bool GLGizmoMmuSegmentation::on_key_down_select_tool_type(int keyCode) { return true; } -static void render_extruders_combo(const std::string &label, - const std::vector &extruders, - const std::vector> &extruders_colors, - size_t &selection_idx) +static void render_extruders_combo(const std::string& label, + const std::vector& extruders, + const std::vector& extruders_colors, + size_t& selection_idx) { assert(!extruders_colors.empty()); assert(extruders_colors.size() == extruders_colors.size()); - auto convert_to_imu32 = [](const std::array &color) -> ImU32 { - return IM_COL32(uint8_t(color[0] * 255.f), uint8_t(color[1] * 255.f), uint8_t(color[2] * 255.f), uint8_t(color[3] * 255.f)); - }; - size_t selection_out = selection_idx; // It is necessary to use BeginGroup(). Otherwise, when using SameLine() is called, then other items will be drawn inside the combobox. ImGui::BeginGroup(); @@ -303,7 +306,7 @@ static void render_extruders_combo(const std::string &labe ImGui::SameLine(); ImGuiStyle &style = ImGui::GetStyle(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), convert_to_imu32(extruders_colors[extruder_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), ImGuiWrapper::to_ImU32(extruders_colors[extruder_idx])); ImGui::GetWindowDrawList()->AddRect(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(start_position.x + height + height / 2 + style.FramePadding.x, start_position.y)); @@ -321,7 +324,7 @@ static void render_extruders_combo(const std::string &labe ImVec2 p = ImGui::GetCursorScreenPos(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), convert_to_imu32(extruders_colors[selection_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), ImGuiWrapper::to_ImU32(extruders_colors[selection_idx])); ImGui::GetWindowDrawList()->AddRect(p, ImVec2(p.x + height + height / 2, p.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(p.x + height + height / 2 + style.FramePadding.x, p.y)); @@ -448,8 +451,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float item_spacing = m_imgui->scaled(0.8f); size_t n_extruder_colors = std::min((size_t)EnforcerBlockerType::ExtruderMax, m_extruders_colors.size()); for (int extruder_idx = 0; extruder_idx < n_extruder_colors; extruder_idx++) { - const std::array &extruder_color = m_extruders_colors[extruder_idx]; - ImVec4 color_vec(extruder_color[0], extruder_color[1], extruder_color[2], extruder_color[3]); + const ColorRGBA &extruder_color = m_extruders_colors[extruder_idx]; + ImVec4 color_vec = ImGuiWrapper::to_ImVec4(extruder_color); std::string color_label = std::string("##extruder color ") + std::to_string(extruder_idx); std::string item_text = std::to_string(extruder_idx + 1); const ImVec2 label_size = ImGui::CalcTextSize(item_text.c_str(), NULL, true); @@ -486,7 +489,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (extruder_idx < 9 && ImGui::IsItemHovered()) m_imgui->tooltip(_L("Shortcut Key ") + std::to_string(extruder_idx + 1), max_tooltip_width); // draw filament id - float gray = 0.299 * extruder_color[0] + 0.587 * extruder_color[1] + 0.114 * extruder_color[2]; + float gray = 0.299 * extruder_color.r() + 0.587 * extruder_color.g() + 0.114 * extruder_color.b(); ImGui::SameLine(button_offset + (button_size.x - label_size.x) / 2.f); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {10.0,15.0}); if (gray * 255.f < 80.f) @@ -573,7 +576,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -586,7 +589,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position(clp_dist, true); } + if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } } else if (m_current_tool == ImGui::TriangleButtonIcon) { m_cursor_type = TriangleSelector::CursorType::POINTER; @@ -599,7 +602,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -612,7 +615,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position(clp_dist, true); } + if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } } else if (m_current_tool == ImGui::FillButtonIcon) { m_cursor_type = TriangleSelector::CursorType::POINTER; @@ -646,7 +649,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -659,7 +662,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position(clp_dist, true);} + if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true);} } else if (m_current_tool == ImGui::HeightRangeIcon) { m_tool_type = ToolType::BRUSH; @@ -682,7 +685,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -695,7 +698,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position(clp_dist, true); } + if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } } else if (m_current_tool == ImGui::GapFillIcon) { m_tool_type = ToolType::GAP_FILL; @@ -811,7 +814,7 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors() continue; int extruder_idx = (mv->extruder_id() > 0) ? mv->extruder_id() - 1 : 0; - std::vector> ebt_colors; + std::vector ebt_colors; ebt_colors.push_back(m_extruders_colors[size_t(extruder_idx)]); ebt_colors.insert(ebt_colors.end(), m_extruders_colors.begin(), m_extruders_colors.end()); @@ -833,7 +836,7 @@ void GLGizmoMmuSegmentation::update_triangle_selectors_colors() TriangleSelectorPatch* selector = dynamic_cast(m_triangle_selectors[i].get()); int extruder_idx = m_volumes_extruder_idxs[i]; int extruder_color_idx = std::max(0, extruder_idx - 1); - std::vector> ebt_colors; + std::vector ebt_colors; ebt_colors.push_back(m_extruders_colors[extruder_color_idx]); ebt_colors.insert(ebt_colors.end(), m_extruders_colors.begin(), m_extruders_colors.end()); selector->set_ebt_colors(ebt_colors); @@ -871,7 +874,7 @@ PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const } // BBS -std::array GLGizmoMmuSegmentation::get_cursor_hover_color() const +ColorRGBA GLGizmoMmuSegmentation::get_cursor_hover_color() const { if (m_selected_extruder_idx < m_extruders_colors.size()) return m_extruders_colors[m_selected_extruder_idx]; @@ -910,6 +913,7 @@ void GLMmSegmentationGizmo3DScene::release_geometry() { glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id)); triangle_indices_VBO_id = 0; } + this->clear(); } @@ -917,28 +921,39 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const { assert(triangle_indices_idx < this->triangle_indices_VBO_ids.size()); assert(this->triangle_patches.size() == this->triangle_indices_VBO_ids.size()); + assert(this->vertices_VAO_id != 0); assert(this->vertices_VBO_id != 0); assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0); + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader == nullptr) + return; + + // the following binding is needed to set the vertex attributes glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float)))); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - // Render using the Vertex Buffer Objects. - if (this->triangle_indices_sizes[triangle_indices_idx] > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[triangle_indices_idx])); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr)); - glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + const GLint position_id = shader->get_attrib_location("v_position"); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0)); + glsafe(::glEnableVertexAttribArray(position_id)); } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + // Render using the Vertex Buffer Objects. + if (this->triangle_indices_VBO_ids[triangle_indices_idx] != 0 && + this->triangle_indices_sizes[triangle_indices_idx] > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[triangle_indices_idx])); + glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } void GLMmSegmentationGizmo3DScene::finalize_vertices() { + assert(this->vertices_VAO_id == 0); assert(this->vertices_VBO_id == 0); if (!this->vertices.empty()) { glsafe(::glGenBuffers(1, &this->vertices_VBO_id)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 6b5cde25c7..d816735c68 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -67,9 +67,9 @@ public: GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoMmuSegmentation() override = default; - void render_painter_gizmo() const override; + void render_painter_gizmo() override; - void set_painter_gizmo_data(const Selection& selection) override; + void data_changed(bool is_serializing) override; void render_triangles(const Selection& selection) const override; @@ -87,7 +87,7 @@ public: protected: // BBS - std::array get_cursor_hover_color() const override; + ColorRGBA get_cursor_hover_color() const override; void on_set_state() override; EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_selected_extruder_idx + 1); } @@ -103,11 +103,11 @@ protected: std::string get_gizmo_entering_text() const override { return "Entering color painting"; } std::string get_gizmo_leaving_text() const override { return "Leaving color painting"; } - std::string get_action_snapshot_name() override { return "Color painting editing"; } + std::string get_action_snapshot_name() const override { return "Color painting editing"; } // BBS size_t m_selected_extruder_idx = 0; - std::vector> m_extruders_colors; + std::vector m_extruders_colors; std::vector m_volumes_extruder_idxs; // BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 747d8ec89e..91913914a8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -1,4 +1,7 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +///|/ Copyright (c) Prusa Research 2019 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoMove.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" @@ -23,16 +26,9 @@ const double GLGizmoMove3D::Offset = 10.0; //BBS: GUI refactor: add obj manipulation GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation) : GLGizmoBase(parent, icon_filename, sprite_id) - , m_displacement(Vec3d::Zero()) - , m_snap_step(1.0) - , m_starting_drag_position(Vec3d::Zero()) - , m_starting_box_center(Vec3d::Zero()) - , m_starting_box_bottom_center(Vec3d::Zero()) //BBS: GUI refactor: add obj manipulation , m_object_manipulation(obj_manipulation) -{ - m_vbo_cone.init_from(its_make_cone(1., 1., 2*PI/36)); -} +{} std::string GLGizmoMove3D::get_tooltip() const { @@ -50,12 +46,24 @@ std::string GLGizmoMove3D::get_tooltip() const return ""; } +bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { + return use_grabbers(mouse_event); +} + +void GLGizmoMove3D::data_changed(bool is_serializing) { + m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower(); +} + bool GLGizmoMove3D::on_init() { for (int i = 0; i < 3; ++i) { m_grabbers.push_back(Grabber()); + m_grabbers.back().extensions = GLGizmoBase::EGrabberExtension::PosZ; } + m_grabbers[0].angles = { 0.0, 0.5 * double(PI), 0.0 }; + m_grabbers[1].angles = { -0.5 * double(PI), 0.0, 0.0 }; + m_shortcut_key = WXK_CONTROL_M; return true; @@ -73,22 +81,23 @@ bool GLGizmoMove3D::on_is_activable() const void GLGizmoMove3D::on_start_dragging() { - if (m_hover_id != -1) { - m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center(2) = box.min(2); - } + assert(m_hover_id != -1); + + m_displacement = Vec3d::Zero(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center(2) = box.min(2); } void GLGizmoMove3D::on_stop_dragging() { + m_parent.do_move(L("Gizmo-Move")); m_displacement = Vec3d::Zero(); } -void GLGizmoMove3D::on_update(const UpdateData& data) +void GLGizmoMove3D::on_dragging(const UpdateData& data) { if (m_hover_id == 0) m_displacement.x() = calc_projection(data); @@ -96,6 +105,9 @@ void GLGizmoMove3D::on_update(const UpdateData& data) m_displacement.y() = calc_projection(data); else if (m_hover_id == 2) m_displacement.z() = calc_projection(data); + + Selection &selection = m_parent.get_selection(); + selection.translate(m_displacement); } void GLGizmoMove3D::on_render() @@ -137,47 +149,63 @@ void GLGizmoMove3D::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - // draw grabbers - for (unsigned int i = 0; i < 3; ++i) { - if (m_grabbers[i].enabled) render_grabber_extension((Axis) i, box, false); - } + auto render_grabber_connection = [this, ¢er](unsigned int id) { + if (m_grabbers[id].enabled) { + //if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(center)) { + m_grabber_connections[id].old_center = center; + m_grabber_connections[id].model.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = AXES_COLOR[id]; + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[id].center.cast()); + + // indices + init_data.add_line(0, 1); + + m_grabber_connections[id].model.init_from(std::move(init_data)); + //} - // draw axes line - // draw axes - for (unsigned int i = 0; i < 3; ++i) { - if (m_grabbers[i].enabled) { - glsafe(::glColor4fv(AXES_COLOR[i].data())); glLineStipple(1, 0x0FFF); glEnable(GL_LINE_STIPPLE); - ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); - // use extension center - ::glVertex3dv(m_grabbers[i].center.data()); - glsafe(::glEnd()); + m_grabber_connections[id].model.render(); glDisable(GL_LINE_STIPPLE); } + }; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + // draw axes + for (unsigned int i = 0; i < 3; ++i) { + render_grabber_connection(i); + } + + shader->stop_using(); } + + // draw grabbers + render_grabbers(box); } -void GLGizmoMove3D::on_render_for_picking() +void GLGizmoMove3D::on_register_raycasters_for_picking() { - glsafe(::glDisable(GL_DEPTH_TEST)); + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); +} - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - //BBS donot render base grabber for picking - //render_grabbers_for_picking(box); - - //get picking colors only - for (unsigned int i = 0; i < (unsigned int) m_grabbers.size(); ++i) { - if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); - m_grabbers[i].color = color; - } - } - - render_grabber_extension(X, box, true); - render_grabber_extension(Y, box, true); - render_grabber_extension(Z, box, true); +void GLGizmoMove3D::on_unregister_raycasters_for_picking() +{ + m_parent.set_raycaster_gizmos_on_top(false); } //BBS: add input window for move @@ -192,17 +220,17 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const { double projection = 0.0; - Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; - double len_starting_vec = starting_vec.norm(); + const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; + const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); + const Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir; // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; + const Vec3d inters_vec = inters - m_starting_drag_position; // finds projection of the vector along the staring direction projection = inters_vec.dot(starting_vec.normalized()); @@ -213,51 +241,5 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const return projection; } - -void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const -{ -#if ENABLE_FIXED_GRABBER - float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif - - double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; - - std::array color = m_grabbers[axis].color; - if (!picking && m_hover_id != -1) { - if (m_hover_id == axis) { - color = m_grabbers[axis].hover_color; - } - } - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - const_cast(&m_vbo_cone)->set_color(-1, color); - if (!picking) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - } - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[axis].center.x(), m_grabbers[axis].center.y(), m_grabbers[axis].center.z())); - if (axis == X) - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); - else if (axis == Y) - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - - //glsafe(::glTranslated(0.0, 0.0, 2.0 * size)); - glsafe(::glScaled(0.75 * size, 0.75 * size, 2.0 * size)); - m_vbo_cone.render(); - glsafe(::glPopMatrix()); - - if (! picking) - shader->stop_using(); -} - - - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 21d19b4465..f4c04c730a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoMove_hpp_ #define slic3r_GLGizmoMove_hpp_ @@ -15,15 +19,18 @@ class GLGizmoMove3D : public GLGizmoBase { static const double Offset; - Vec3d m_displacement; + Vec3d m_displacement{ Vec3d::Zero() }; + double m_snap_step{ 1.0 }; + Vec3d m_starting_drag_position{ Vec3d::Zero() }; + Vec3d m_starting_box_center{ Vec3d::Zero() }; + Vec3d m_starting_box_bottom_center{ Vec3d::Zero() }; - double m_snap_step; - - Vec3d m_starting_drag_position; - Vec3d m_starting_box_center; - Vec3d m_starting_box_bottom_center; - - GLModel m_vbo_cone; + struct GrabberConnection + { + GLModel model; + Vec3d old_center{ Vec3d::Zero() }; + }; + std::array m_grabber_connections; //BBS: add size adjust related GizmoObjectManipulation* m_object_manipulation; @@ -37,25 +44,34 @@ public: double get_snap_step(double step) const { return m_snap_step; } void set_snap_step(double step) { m_snap_step = step; } - const Vec3d& get_displacement() const { return m_displacement; } - std::string get_tooltip() const override; + /// + /// Postpone to Grabber for move + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + /// + /// Detect reduction of move for wipetover on selection change + /// + void data_changed(bool is_serializing) override; protected: - virtual bool on_init() override; - virtual std::string on_get_name() const override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; - virtual void on_stop_dragging() override; - virtual void on_update(const UpdateData& data) override; - virtual void on_render() override; - virtual void on_render_for_picking() override; + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_dragging(const UpdateData& data) override; + void on_render() override; + void on_register_raycasters_for_picking() override; + void on_unregister_raycasters_for_picking() override; //BBS: GUI refactor: add object manipulation virtual void on_render_input_window(float x, float y, float bottom_limit); private: double calc_projection(const UpdateData& data) const; - void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index f682d249ec..6bf8a526ab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1,4 +1,7 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Lukáš Hejl @hejllukas +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoPainterBase.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" @@ -8,6 +11,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/OpenGLManager.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -18,24 +22,28 @@ namespace Slic3r::GUI { +std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { - // Make sphere and save it into a vertex buffer. - m_vbo_sphere.load_its_flat_shading(its_make_sphere(1., (2*M_PI)/24.)); - m_vbo_sphere.finalize_geometry(true); m_vertical_only = false; m_horizontal_only = false; } -void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) +GLGizmoPainterBase::~GLGizmoPainterBase() +{ + if (s_sphere != nullptr) + s_sphere.reset(); +} + +void GLGizmoPainterBase::data_changed(bool is_serializing) { if (m_state != On) return; const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; - + const Selection & selection = m_parent.get_selection(); if (mo && selection.is_from_single_instance() && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) { @@ -101,8 +109,12 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const if (is_left_handed) glsafe(::glFrontFace(GL_CW)); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); // For printers with multiple extruders, it is necessary to pass trafo_matrix // to the shader input variable print_box.volume_world_matrix before @@ -110,16 +122,13 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const // wrong transformation matrix is used for "Clipping of view". shader->set_uniform("volume_world_matrix", trafo_matrix); - m_triangle_selectors[mesh_id]->render(m_imgui); - - glsafe(::glPopMatrix()); + m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); } } - -void GLGizmoPainterBase::render_cursor() const +void GLGizmoPainterBase::render_cursor() { // First check that the mouse pointer is on an object. const ModelObject* mo = m_c->selection_info()->model_object(); @@ -157,92 +166,127 @@ void GLGizmoPainterBase::render_cursor() const } } - - -void GLGizmoPainterBase::render_cursor_circle() const +void GLGizmoPainterBase::render_cursor_circle() { - const Camera &camera = wxGetApp().plater()->get_camera(); - auto zoom = (float) camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - - Size cnv_size = m_parent.get_canvas_size(); - float cnv_half_width = 0.5f * (float) cnv_size.get_width(); - float cnv_half_height = 0.5f * (float) cnv_size.get_height(); - if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) + const Size cnv_size = m_parent.get_canvas_size(); + const float cnv_width = float(cnv_size.get_width()); + const float cnv_height = float(cnv_size.get_height()); + if (cnv_width == 0.0f || cnv_height == 0.0f) return; - Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1)); - Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1)); - center = center * inv_zoom; + + const float cnv_inv_width = 1.0f / cnv_width; + const float cnv_inv_height = 1.0f / cnv_height; + + const Vec2d center = m_parent.get_local_mouse_position(); + const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom()); glsafe(::glLineWidth(1.5f)); - - // BBS - std::array render_color = this->get_cursor_hover_color(); - if (m_button_down == Button::Left) - render_color = this->get_cursor_sphere_left_button_color(); - else if (m_button_down == Button::Right) - render_color = this->get_cursor_sphere_right_button_color(); - glsafe(::glColor4fv(render_color.data())); - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the circle is renderered inside the frustrum - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); - // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); - glsafe(::glPushAttrib(GL_ENABLE_BIT)); glsafe(::glLineStipple(4, 0xAAAA)); glsafe(::glEnable(GL_LINE_STIPPLE)); - ::glBegin(GL_LINE_LOOP); - for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) - ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); - glsafe(::glEnd()); + if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) { + m_old_cursor_radius = radius; + m_old_center = center; + m_circle.reset(); + + GLModel::Geometry init_data; + static const unsigned int StepsCount = 32; + static const float StepSize = 2.0f * float(PI) / float(StepsCount); + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; + init_data.reserve_vertices(StepsCount); + init_data.reserve_indices(StepsCount); + + // vertices + indices + for (unsigned int i = 0; i < StepsCount; ++i) { + const float angle = float(i * StepSize); + init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f), + -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f))); + init_data.add_index(i); + } + + m_circle.init_from(std::move(init_data)); + } + + // BBS + ColorRGBA render_color = this->get_cursor_hover_color(); + if (m_button_down == Button::Left) + render_color = this->get_cursor_sphere_left_button_color(); + else if (m_button_down == Button::Right) + render_color = this->get_cursor_sphere_right_button_color(); + + m_circle.set_color(render_color); + + GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + m_circle.render(); + shader->stop_using(); + } glsafe(::glPopAttrib()); - glsafe(::glPopMatrix()); glsafe(::glEnable(GL_DEPTH_TEST)); } void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const { + if (s_sphere == nullptr) { + s_sphere = std::make_shared(); + s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); - const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); - - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo.data())); - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2))); - glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data())); - glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); - - if (is_left_handed) - glFrontFace(GL_CW); // BBS - std::array render_color = this->get_cursor_hover_color(); + ColorRGBA render_color = this->get_cursor_hover_color(); if (m_button_down == Button::Left) render_color = this->get_cursor_sphere_left_button_color(); else if (m_button_down == Button::Right) render_color = this->get_cursor_sphere_right_button_color(); - glsafe(::glColor4fv(render_color.data())); - m_vbo_sphere.render(); + shader->start_using(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * trafo * + Geometry::assemble_transform(m_rr.hit.cast()) * complete_scaling_matrix_inverse * + Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_cursor_radius * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + const bool is_left_handed = Geometry::Transformation(view_model_matrix).is_left_handed(); + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + assert(s_sphere != nullptr); + s_sphere->set_color(render_color); + s_sphere->render(); if (is_left_handed) - glFrontFace(GL_CCW); + glsafe(::glFrontFace(GL_CCW)); - glsafe(::glPopMatrix()); + shader->stop_using(); } // BBS void GLGizmoPainterBase::render_cursor_height_range(const Transform3d& trafo) const { + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + const BoundingBoxf3 box = bounding_box(); Vec3d hit_world = trafo * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); float max_z = (float)box.max.z(); @@ -268,14 +312,18 @@ void GLGizmoPainterBase::render_cursor_height_range(const Transform3d& trafo) co for (int i = 0; i < zs.size(); i++) { update_contours(vol_mesh, zs[i], max_z, min_z); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(m_cut_contours.shift); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); glsafe(::glLineWidth(2.0f)); m_cut_contours.contours.render(); - glsafe(::glPopMatrix()); } } + + shader->stop_using(); } BoundingBoxf3 GLGizmoPainterBase::bounding_box() const @@ -294,7 +342,7 @@ BoundingBoxf3 GLGizmoPainterBase::bounding_box() const void GLGizmoPainterBase::update_contours(const TriangleMesh& vol_mesh, float cursor_z, float max_z, float min_z) const { const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* first_glvolume = selection.get_first_volume(); const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; @@ -317,7 +365,7 @@ void GLGizmoPainterBase::update_contours(const TriangleMesh& vol_mesh, float cur const Polygons polys = slice_mesh(m_cut_contours.mesh.its, cursor_z, slicing_params); if (!polys.empty()) { m_cut_contours.contours.init_from(polys, static_cast(cursor_z)); - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); + m_cut_contours.contours.set_color({ 1.0f, 1.0f, 1.0f, 1.0f }); } } else if (box.center() != m_cut_contours.position) { @@ -584,13 +632,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous pos = action == SLAGizmoEventType::MouseWheelDown ? std::max(0., pos - 0.01) : std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); + m_c->object_clipper()->set_position_by_ratio(pos, true); return true; } } if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); return true; } @@ -741,14 +789,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE); if (projected_mouse_positions.size() == 1) { - const ProjectedMousePosition& first_position = projected_mouse_positions.front(); - std::unique_ptr cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit, - camera_pos, m_cursor_radius, - m_cursor_type, trafo_matrix, clp); + const ProjectedMousePosition &first_position = projected_mouse_positions.front(); + std::unique_ptr cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit, + camera_pos, m_cursor_radius, + m_cursor_type, trafo_matrix, clp); m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, - m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); - } - else { + m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } else { for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) { auto second_position_it = first_position_it + 1; std::unique_ptr cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp); @@ -853,7 +900,72 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return false; } +bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event) +{ + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + if (mouse_event.Moving()) { + gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false); + return false; + } + + // when control is down we allow scene pan and rotation even when clicking + // over some object + bool control_down = mouse_event.CmdDown(); + bool grabber_contains_mouse = (get_hover_id() != -1); + + const Selection &selection = m_parent.get_selection(); + int selected_object_idx = selection.get_object_idx(); + if (mouse_event.LeftDown()) { + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + // the gizmo got the event and took some action, there is no need + // to do anything more + return true; + } else if (mouse_event.RightDown()){ + if (!control_down && selected_object_idx != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) + // event was taken care of + return true; + } else if (mouse_event.Dragging()) { + if (m_parent.get_move_volume_id() != -1) + // don't allow dragging objects with the Sla gizmo on + return true; + if (!control_down && gizmo_event(SLAGizmoEventType::Dragging, + mouse_pos, mouse_event.ShiftDown(), + mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } + if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) + { + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) + gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + return false; + } + } else if (mouse_event.LeftUp()) { + if (!m_parent.is_mouse_dragging()) { + // in case SLA/FDM gizmo is selected, we just pass the LeftUp + // event and stop processing - neither object moving or selecting + // is suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } else if (mouse_event.RightUp()) { + if (!m_parent.is_mouse_dragging()) { + gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } + return false; +} void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position, const Camera& camera, @@ -1002,17 +1114,20 @@ TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed}); } -std::array TriangleSelectorGUI::get_seed_fill_color(const std::array &base_color) +ColorRGBA TriangleSelectorGUI::enforcers_color = {0.5f, 1.f, 0.5f, 1.f}; +ColorRGBA TriangleSelectorGUI::blockers_color = {1.f, 0.5f, 0.5f, 1.f}; + +ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color) { // BBS return { - base_color[0] * 1.25f < 1.f ? base_color[0] * 1.25f : 1.f, - base_color[1] * 1.25f < 1.f ? base_color[1] * 1.25f : 1.f, - base_color[2] * 1.25f < 1.f ? base_color[2] * 1.25f : 1.f, + base_color.r() * 1.25f < 1.f ? base_color.r() * 1.25f : 1.f, + base_color.g() * 1.25f < 1.f ? base_color.g() * 1.25f : 1.f, + base_color.b() * 1.25f < 1.f ? base_color.b() * 1.25f : 1.f, 1.f}; } -void TriangleSelectorGUI::render(ImGuiWrapper* imgui) +void TriangleSelectorGUI::render(ImGuiWrapper* imgui, const Transform3d& matrix) { if (m_update_render_data) { update_render_data(); @@ -1022,41 +1137,25 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) auto* shader = wxGetApp().get_current_shader(); if (! shader) return; - assert(shader->get_name() == "gouraud"); - ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);}); - shader->set_uniform("offset_depth_buffer", true); + assert(shader->get_name() == "gouraud" || shader->get_name() == "mm_gouraud"); + for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), std::make_pair(&m_iva_blockers, blockers_color)}) { - if (iva.first->has_VBOs()) { - shader->set_uniform("uniform_color", iva.second); - iva.first->render(); - } + iva.first->set_color(iva.second); + iva.first->render(); } - for (auto &iva : m_iva_seed_fills) - if (iva.has_VBOs()) { - size_t color_idx = &iva - &m_iva_seed_fills.front(); - const std::array &color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : - color_idx == 2 ? blockers_color : - GLVolume::NEUTRAL_COLOR); - shader->set_uniform("uniform_color", color); - iva.render(); - } - - if (m_paint_contour.has_VBO()) { - ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); - shader->stop_using(); - - auto *contour_shader = wxGetApp().get_shader("mm_contour"); - contour_shader->start_using(); - - glsafe(::glDepthFunc(GL_LEQUAL)); - m_paint_contour.render(); - glsafe(::glDepthFunc(GL_LESS)); - - contour_shader->stop_using(); + for (auto& iva : m_iva_seed_fills) { + size_t color_idx = &iva - &m_iva_seed_fills.front(); + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + color_idx == 2 ? blockers_color : + GLVolume::NEUTRAL_COLOR); + iva.set_color(color); + iva.render(); } + render_paint_contour(matrix); + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) render_debug(imgui); @@ -1071,24 +1170,33 @@ void TriangleSelectorGUI::update_render_data() int blc_cnt = 0; std::vector seed_fill_cnt(m_iva_seed_fills.size(), 0); - for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) - iva->release_geometry(); + for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) { + iva->reset(); + } - for (auto &iva : m_iva_seed_fills) - iva.release_geometry(); + for (auto& iva : m_iva_seed_fills) { + iva.reset(); + } + + GLModel::Geometry iva_enforcers_data; + iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + GLModel::Geometry iva_blockers_data; + iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + std::array iva_seed_fills_data; + for (auto& data : iva_seed_fills_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + + // small value used to offset triangles along their normal to avoid z-fighting + static const float offset = 0.001f; for (const Triangle &tr : m_triangles) { - bool is_valid = tr.valid(); - bool is_split = tr.is_split(); - EnforcerBlockerType type = tr.get_state(); - bool is_select_by_seed_fill = tr.is_selected_by_seed_fill(); if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) continue; int tr_state = int(tr.get_state()); - GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : - m_iva_blockers; + GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : + iva_blockers_data; int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt; @@ -1098,37 +1206,25 @@ void TriangleSelectorGUI::update_render_data() //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort // or the current implementation may be more cache friendly. const Vec3f n = (v1 - v0).cross(v2 - v1).normalized(); - iva.push_geometry(v0, n); - iva.push_geometry(v1, n); - iva.push_geometry(v2, n); - iva.push_triangle(cnt, cnt + 1, cnt + 2); + // small value used to offset triangles along their normal to avoid z-fighting + const Vec3f offset_n = offset * n; + iva.add_vertex(v0 + offset_n, n); + iva.add_vertex(v1 + offset_n, n); + iva.add_vertex(v2 + offset_n, n); + iva.add_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2); cnt += 3; } - for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) - iva->finalize_geometry(true); - - for (auto &iva : m_iva_seed_fills) - iva.finalize_geometry(true); - - m_paint_contour.release_geometry(); - std::vector contour_edges = this->get_seed_fill_contour(); - m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); - for (const Vec2i &edge : contour_edges) { - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); - - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); + if (!iva_enforcers_data.is_empty()) + m_iva_enforcers.init_from(std::move(iva_enforcers_data)); + if (!iva_blockers_data.is_empty()) + m_iva_blockers.init_from(std::move(iva_blockers_data)); + for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) { + if (!iva_seed_fills_data[i].is_empty()) + m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i])); } - m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); - std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); - m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); - - m_paint_contour.finalize_geometry(); + update_paint_contour(); } // BBS @@ -1139,68 +1235,39 @@ bool TrianglePatch::is_fragment() const float TriangleSelectorPatch::gap_area = TriangleSelectorPatch::GapAreaMin; -void TriangleSelectorPatch::render(ImGuiWrapper* imgui) +void TriangleSelectorPatch::render(ImGuiWrapper* imgui, const Transform3d& matrix) { - if (m_update_render_data) + if (m_update_render_data) { update_render_data(); + m_update_render_data = false; + } auto* shader = wxGetApp().get_current_shader(); if (!shader) return; - assert(shader->get_name() == "mm_gouraud"); - GLint position_id = -1; - GLint barycentric_id = -1; - if (wxGetApp().plater()->is_wireframe_enabled()) { - position_id = shader->get_attrib_location("v_position"); - barycentric_id = shader->get_attrib_location("v_barycentric"); - if (m_need_wireframe && wxGetApp().plater()->is_show_wireframe()) { - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", show_wireframe on"); - shader->set_uniform("show_wireframe", true); - } - else { - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", show_wireframe off"); - shader->set_uniform("show_wireframe", false); - } - } + assert(shader->get_name() == "gouraud" || shader->get_name() == "mm_gouraud"); for (size_t buffer_idx = 0; buffer_idx < m_triangle_patches.size(); ++buffer_idx) { if (this->has_VBOs(buffer_idx)) { const TrianglePatch& patch = m_triangle_patches[buffer_idx]; - std::array color; + ColorRGBA color; if (patch.is_fragment() && !patch.neighbor_types.empty()) { size_t color_idx = (size_t)*patch.neighbor_types.begin(); color = m_ebt_colors[color_idx]; - color[3] = 0.85; + color.a(0.85); } else { size_t color_idx = (size_t)patch.type; color = m_ebt_colors[color_idx]; } //to make black not too hard too see - std::array new_color = adjust_color_for_rendering(color); + ColorRGBA new_color = adjust_color_for_rendering(color); shader->set_uniform("uniform_color", new_color); - //shader->set_uniform("uniform_color", color); - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", buffer_idx %1%: new_color[%2%, %3%, %4%, %5%]")%buffer_idx%new_color[0]%new_color[1]%new_color[2]%new_color[3]; - this->render(buffer_idx, (int)position_id, (int)barycentric_id); + this->render(buffer_idx); } } - if (m_paint_contour.has_VBO()) - { - ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); }); - shader->stop_using(); - - auto* contour_shader = wxGetApp().get_shader("mm_contour"); - contour_shader->start_using(); - - glsafe(::glDepthFunc(GL_LEQUAL)); - m_paint_contour.render(); - glsafe(::glDepthFunc(GL_LESS)); - - contour_shader->stop_using(); - } - - m_update_render_data = false; + render_paint_contour(matrix); } void TriangleSelectorPatch::update_triangles_per_type() @@ -1423,59 +1490,32 @@ void TriangleSelectorPatch::update_render_data() } //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", before paint_contour"); - - m_paint_contour.release_geometry(); - std::vector contour_edges = this->get_seed_fill_contour(); - m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); - for (const Vec2i& edge : contour_edges) { - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); - - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); - } - - m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); - std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); - m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); - - m_paint_contour.finalize_geometry(); + update_paint_contour(); //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", exit"); } -void TriangleSelectorPatch::render(int triangle_indices_idx, int position_id, int barycentric_id) +void TriangleSelectorPatch::render(int triangle_indices_idx) { assert(triangle_indices_idx < this->m_triangle_indices_VBO_ids.size()); assert(this->m_triangle_patches.size() == this->m_triangle_indices_VBO_ids.size()); //assert(this->m_vertices_VBO_id != 0); assert(this->m_triangle_patches.size() == this->m_vertices_VBO_ids.size()); + assert(this->m_vertices_VAO_ids[triangle_indices_idx] != 0); assert(this->m_vertices_VBO_ids[triangle_indices_idx] != 0); assert(this->m_triangle_indices_VBO_ids[triangle_indices_idx] != 0); - //glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_vertices_VBO_id)); - //glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float)))); - if (this->m_triangle_indices_sizes[triangle_indices_idx] > 0) { - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_vertices_VBO_ids[triangle_indices_idx])); - if (position_id != -1) { - glsafe(::glEnableVertexAttribArray((GLint)position_id)); - glsafe(::glVertexAttribPointer((GLint)position_id, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), nullptr)); - } - else { - glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); - } + GLShaderProgram *shader = wxGetApp().get_current_shader(); + if (shader == nullptr) + return; - if (barycentric_id != -1) { - glsafe(::glEnableVertexAttribArray((GLint)barycentric_id)); - glsafe(::glVertexAttribPointer((GLint)barycentric_id, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (GLvoid*)(intptr_t)(3 * sizeof(float)))); - } - //glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: triangle_indices_idx %2%, bind vertex vbo, buffer id %3%")%__LINE__%triangle_indices_idx%this->m_vertices_VBO_ids[triangle_indices_idx]; + // the following binding is needed to set the vertex attributes + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_vertices_VBO_ids[triangle_indices_idx])); + const GLint position_id = shader->get_attrib_location("v_position"); + if (position_id != -1) { + glsafe(::glVertexAttribPointer((GLint) position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr)); + glsafe(::glEnableVertexAttribArray((GLint)position_id)); } - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - // Render using the Vertex Buffer Objects. if (this->m_triangle_indices_sizes[triangle_indices_idx] > 0) { glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_triangle_indices_VBO_ids[triangle_indices_idx])); @@ -1484,14 +1524,10 @@ void TriangleSelectorPatch::render(int triangle_indices_idx, int position_id, in //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: triangle_indices_idx %2%, bind indices vbo, buffer id %3%")%__LINE__%triangle_indices_idx%this->m_triangle_indices_VBO_ids[triangle_indices_idx]; } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - if ((this->m_triangle_indices_sizes[triangle_indices_idx] > 0)&&(position_id != -1)) + if (position_id != -1) glsafe(::glDisableVertexAttribArray(position_id)); - if ((this->m_triangle_indices_sizes[triangle_indices_idx] > 0)&&(barycentric_id != -1)) - glsafe(::glDisableVertexAttribArray((GLint)barycentric_id)); - if (this->m_triangle_indices_sizes[triangle_indices_idx] > 0) - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } void TriangleSelectorPatch::release_geometry() @@ -1556,64 +1592,6 @@ void TriangleSelectorPatch::finalize_triangle_indices() } } -void GLPaintContour::render() const -{ - assert(this->m_contour_VBO_id != 0); - assert(this->m_contour_EBO_id != 0); - - glsafe(::glLineWidth(4.0f)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - if (this->contour_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); - glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -} - -void GLPaintContour::finalize_geometry() -{ - assert(this->m_contour_VBO_id == 0); - assert(this->m_contour_EBO_id == 0); - - if (!this->contour_vertices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->contour_vertices.clear(); - } - - if (!this->contour_indices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - this->contour_indices.clear(); - } -} - -void GLPaintContour::release_geometry() -{ - if (this->m_contour_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); - this->m_contour_VBO_id = 0; - } - if (this->m_contour_EBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); - this->m_contour_EBO_id = 0; - } - this->clear(); -} - #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) { @@ -1650,59 +1628,124 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) }; for (auto& va : m_varrays) - va.release_geometry(); + va.reset(); std::array cnts; ::glScalef(1.01f, 1.01f, 1.01f); + std::array varrays_data; + for (auto& data : varrays_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; + for (int tr_id=0; tr_idpush_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), - double(m_vertices[tr.verts_idxs[i]].v[1]), - double(m_vertices[tr.verts_idxs[i]].v[2]), - 0., 0., 1.); - va->push_triangle(*cnt, - *cnt+1, - *cnt+2); + for (int i = 0; i < 3; ++i) { + va->add_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f)); + } + va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2); *cnt += 3; } + for (int i = 0; i < 3; ++i) { + if (!varrays_data[i].is_empty()) + m_varrays[i].init_from(std::move(varrays_data[i])); + } + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); for (vtype i : {ORIGINAL, SPLIT, INVALID}) { - GLIndexedVertexArray& va = m_varrays[i]; - va.finalize_geometry(true); - if (va.has_VBOs()) { - switch (i) { - case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; - case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; - case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; - } - va.render(); + GLModel& va = m_varrays[i]; + switch (i) { + case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break; + case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break; + case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break; } + va.render(); } ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); } -#endif +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +void TriangleSelectorGUI::update_paint_contour() +{ + m_paint_contour.reset(); + GLModel::Geometry init_data; + const std::vector contour_edges = this->get_seed_fill_contour(); + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * contour_edges.size()); + init_data.reserve_indices(2 * contour_edges.size()); + init_data.color = ColorRGBA::WHITE(); + // vertices + indices + unsigned int vertices_count = 0; + for (const Vec2i& edge : contour_edges) { + init_data.add_vertex(m_vertices[edge(0)].v); + init_data.add_vertex(m_vertices[edge(1)].v); + vertices_count += 2; + init_data.add_line(vertices_count - 2, vertices_count - 1); + } + + if (!init_data.is_empty()) + m_paint_contour.init_from(std::move(init_data)); +} + +void TriangleSelectorGUI::render_paint_contour(const Transform3d& matrix) +{ + auto* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + auto* contour_shader = wxGetApp().get_shader("mm_contour"); + if (contour_shader != nullptr) { + contour_shader->start_using(); + + contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); + const Camera& camera = wxGetApp().plater()->get_camera(); + contour_shader->set_uniform("view_model_matrix", camera.get_view_matrix() * matrix); + contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + m_paint_contour.render(); + + contour_shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +} } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index d2f69ad2ae..4ea9412ffa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -1,9 +1,13 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Pavel Mikuš @Godrak, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoPainterBase_hpp_ #define slic3r_GLGizmoPainterBase_hpp_ #include "GLGizmoBase.hpp" -#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/GLModel.hpp" #include "libslic3r/ObjectID.hpp" #include "libslic3r/TriangleSelector.hpp" @@ -12,6 +16,7 @@ #include #include +#include namespace Slic3r::GUI { @@ -20,6 +25,7 @@ enum class SLAGizmoEventType : unsigned char; class ClippingPlane; struct Camera; class GLGizmoMmuSegmentation; +class Selection; enum class PainterGizmoType { FDM_SUPPORTS, @@ -27,52 +33,14 @@ enum class PainterGizmoType { MMU_SEGMENTATION }; -class GLPaintContour -{ -public: - GLPaintContour() = default; - - void render() const; - - inline bool has_VBO() const { return this->m_contour_EBO_id != 0; } - - // Release the geometry data, release OpenGL VBOs. - void release_geometry(); - - // Finalize the initialization of the contour geometry and the indices, upload both to OpenGL VBO objects - // and possibly releasing it if it has been loaded into the VBOs. - void finalize_geometry(); - - void clear() - { - this->contour_vertices.clear(); - this->contour_indices.clear(); - this->contour_indices_size = 0; - } - - std::vector contour_vertices; - std::vector contour_indices; - - // When the triangle indices are loaded into the graphics card as Vertex Buffer Objects, - // the above mentioned std::vectors are cleared and the following variables keep their original length. - size_t contour_indices_size{0}; - - // IDs of the Vertex Array Objects, into which the geometry has been loaded. - // Zero if the VBOs are not sent to GPU yet. - GLuint m_contour_VBO_id{0}; - GLuint m_contour_EBO_id{0}; -}; - class TriangleSelectorGUI : public TriangleSelector { public: explicit TriangleSelectorGUI(const TriangleMesh& mesh, float edge_limit = 0.6f) : TriangleSelector(mesh, edge_limit) {} virtual ~TriangleSelectorGUI() = default; - // Render current selection. Transformation matrices are supposed - // to be already set. - virtual void render(ImGuiWrapper *imgui); - void render() { this->render(nullptr); } + virtual void render(ImGuiWrapper* imgui, const Transform3d& matrix); + //void render(const Transform3d& matrix) { this->render(nullptr, matrix); } void set_wireframe_needed(bool need_wireframe) { m_need_wireframe = need_wireframe; } bool get_wireframe_needed() { return m_need_wireframe; } @@ -81,11 +49,11 @@ public: { m_update_render_data = true; m_paint_changed |= paint_changed; - }; + } // BBS - static constexpr std::array enforcers_color{ 0.5f, 1.f, 0.5f, 1.f }; - static constexpr std::array blockers_color{ 1.f, 0.5f, 0.5f, 1.f }; + static ColorRGBA enforcers_color; + static ColorRGBA blockers_color; #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); @@ -98,18 +66,24 @@ protected: // BBS bool m_paint_changed = true; - static std::array get_seed_fill_color(const std::array &base_color); + static ColorRGBA get_seed_fill_color(const ColorRGBA &base_color); private: void update_render_data(); - GLIndexedVertexArray m_iva_enforcers; - GLIndexedVertexArray m_iva_blockers; - std::array m_iva_seed_fills; - std::array m_varrays; + GLModel m_iva_enforcers; + GLModel m_iva_blockers; + std::array m_iva_seed_fills; +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + std::array m_varrays; +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG protected: - GLPaintContour m_paint_contour; + GLModel m_paint_contour; + + void update_paint_contour(); + void render_paint_contour(const Transform3d& matrix); + bool m_need_wireframe {false}; }; @@ -128,20 +102,20 @@ struct TrianglePatch { class TriangleSelectorPatch : public TriangleSelectorGUI { public: - explicit TriangleSelectorPatch(const TriangleMesh& mesh, const std::vector> ebt_colors, float edge_limit = 0.6f) + explicit TriangleSelectorPatch(const TriangleMesh& mesh, const std::vector ebt_colors, float edge_limit = 0.6f) : TriangleSelectorGUI(mesh, edge_limit), m_ebt_colors(ebt_colors) {} virtual ~TriangleSelectorPatch() = default; // Render current selection. Transformation matrices are supposed // to be already set. - void render(ImGuiWrapper* imgui) override; + void render(ImGuiWrapper* imgui, const Transform3d& matrix) override; // TriangleSelector.m_triangles => m_gizmo_scene.triangle_patches void update_triangles_per_type(); // m_gizmo_scene.triangle_patches => TriangleSelector.m_triangles void update_selector_triangles(); void update_triangles_per_patch(); - void set_ebt_colors(const std::vector> ebt_colors) { m_ebt_colors = ebt_colors; } + void set_ebt_colors(const std::vector ebt_colors) { m_ebt_colors = ebt_colors; } void set_filter_state(bool is_filter_state); constexpr static float GapAreaMin = 0.f; @@ -195,13 +169,13 @@ protected: std::vector m_vertices_VBO_ids; std::vector m_triangle_indices_VBO_ids; - std::vector> m_ebt_colors; + std::vector m_ebt_colors; bool m_filter_state = false; private: void update_render_data(); - void render(int buffer_idx, int position_id = -1, int barycentric_id = -1); + void render(int buffer_idx); }; @@ -214,18 +188,18 @@ private: ObjectID m_old_mo_id; size_t m_old_volumes_size = 0; void on_render() override {} - void on_render_for_picking() override {} + public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - ~GLGizmoPainterBase() override = default; - virtual void set_painter_gizmo_data(const Selection& selection); + ~GLGizmoPainterBase() override; + void data_changed(bool is_serializing) override; virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); // Following function renders the triangles and cursor. Having this separated // from usual on_render method allows to render them before transparent // objects, so they can be seen inside them. The usual on_render is called // after all volumes (including transparent ones) are rendered. - virtual void render_painter_gizmo() const = 0; + virtual void render_painter_gizmo() = 0; virtual const float get_cursor_radius_min() const { return CursorRadiusMin; } virtual const float get_cursor_radius_max() const { return CursorRadiusMax; } @@ -236,10 +210,19 @@ public: virtual const float get_cursor_height_max() const { return CursorHeightMax; } virtual const float get_cursor_height_step() const { return CursorHeightStep; } + /// + /// Implement when want to process mouse events in gizmo + /// Click, Right click, move, drag, ... + /// + /// Keep information about mouse click + /// Return True when use the information and don't want to + /// propagate it otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + protected: virtual void render_triangles(const Selection& selection) const; - void render_cursor() const; - void render_cursor_circle() const; + void render_cursor(); + void render_cursor_circle(); void render_cursor_sphere(const Transform3d& trafo) const; // BBS void render_cursor_height_range(const Transform3d& trafo) const; @@ -247,10 +230,10 @@ protected: virtual void update_model_object() = 0; virtual void update_from_model_object(bool first_update) = 0; - virtual std::array get_cursor_sphere_left_button_color() const { return {0.f, 0.f, 1.f, 0.25f}; } - virtual std::array get_cursor_sphere_right_button_color() const { return {1.f, 0.f, 0.f, 0.25f}; } + virtual ColorRGBA get_cursor_sphere_left_button_color() const { return { 0.0f, 0.0f, 1.0f, 0.25f }; } + virtual ColorRGBA get_cursor_sphere_right_button_color() const { return { 1.0f, 0.0f, 0.0f, 0.25f }; } // BBS - virtual std::array get_cursor_hover_color() const { return { 0.f, 0.f, 0.f, 0.25f }; } + virtual ColorRGBA get_cursor_hover_color() const { return { 0.f, 0.f, 0.f, 0.25f }; } virtual EnforcerBlockerType get_left_button_state_type() const { return EnforcerBlockerType::ENFORCER; } virtual EnforcerBlockerType get_right_button_state_type() const { return EnforcerBlockerType::BLOCKER; } @@ -300,6 +283,9 @@ protected: bool m_paint_on_overhangs_only = false; float m_highlight_by_angle_threshold_deg = 0.f; + GLModel m_circle; + Vec2d m_old_center{ Vec2d::Zero() }; + float m_old_cursor_radius{ 0.0f }; static constexpr float SmartFillAngleMin = 0.0f; static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; @@ -338,7 +324,7 @@ private: const Camera& camera, const std::vector& trafo_matrices) const; - GLIndexedVertexArray m_vbo_sphere; + static std::shared_ptr s_sphere; bool m_internal_stack_active = false; bool m_schedule_update = false; @@ -356,7 +342,7 @@ private: Vec3f hit; size_t facet; }; - mutable RaycastResult m_rr; + mutable RaycastResult m_rr = {Vec2d::Zero(), -1, Vec3f::Zero(), 0}; // BBS struct CutContours @@ -376,9 +362,6 @@ private: protected: void on_set_state() override; - void on_start_dragging() override {} - void on_stop_dragging() override {} - virtual void on_opening() = 0; virtual void on_shutdown() = 0; virtual PainterGizmoType get_painter_type() const = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 65479b064e..1b412c847c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -1,4 +1,7 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" @@ -17,10 +20,9 @@ namespace GUI { const float GLGizmoRotate::Offset = 5.0f; -const unsigned int GLGizmoRotate::CircleResolution = 64; const unsigned int GLGizmoRotate::AngleResolution = 64; const unsigned int GLGizmoRotate::ScaleStepsCount = 72; -const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; +const float GLGizmoRotate::ScaleStepRad = 2.0f * float(PI) / GLGizmoRotate::ScaleStepsCount; const unsigned int GLGizmoRotate::ScaleLongEvery = 2; const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius const unsigned int GLGizmoRotate::SnapRegionsCount = 8; @@ -30,33 +32,20 @@ const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) : GLGizmoBase(parent, "", -1) , m_axis(axis) - , m_angle(0.0) - , m_center(0.0, 0.0, 0.0) - , m_radius(0.0f) - , m_snap_coarse_in_radius(0.0f) - , m_snap_coarse_out_radius(0.0f) - , m_snap_fine_in_radius(0.0f) - , m_snap_fine_out_radius(0.0f) + , m_drag_color(DEFAULT_DRAG_COLOR) + , m_highlight_color(DEFAULT_HIGHLIGHT_COLOR) { + m_group_id = static_cast(axis); } -GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) - : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) - , m_axis(other.m_axis) - , m_angle(other.m_angle) - , m_center(other.m_center) - , m_radius(other.m_radius) - , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) - , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) - , m_snap_fine_in_radius(other.m_snap_fine_in_radius) - , m_snap_fine_out_radius(other.m_snap_fine_out_radius) +void GLGizmoRotate::set_highlight_color(const ColorRGBA &color) { + m_highlight_color = color; } - void GLGizmoRotate::set_angle(double angle) { - if (std::abs(angle - 2.0 * (double)PI) < EPSILON) + if (std::abs(angle - 2.0 * double(PI)) < EPSILON) angle = 0.0; m_angle = angle; @@ -71,12 +60,35 @@ std::string GLGizmoRotate::get_tooltip() const case Y: { axis = "Y"; break; } case Z: { axis = "Z"; break; } } - return (m_hover_id == 0 || m_grabbers[0].dragging) ? axis + ": " + format((float)Geometry::rad2deg(m_angle), 2) : ""; + return (m_hover_id == 0 || m_grabbers.front().dragging) ? axis + ": " + format(float(Geometry::rad2deg(m_angle)), 2) : ""; } +bool GLGizmoRotate::on_mouse(const wxMouseEvent &mouse_event) +{ + return use_grabbers(mouse_event); +} + +void GLGizmoRotate::dragging(const UpdateData &data) { on_dragging(data); } + +void GLGizmoRotate::start_dragging() +{ + m_grabbers[0].dragging = true; + on_start_dragging(); +} + +void GLGizmoRotate::stop_dragging() +{ + m_grabbers[0].dragging = false; + on_stop_dragging(); +} + +void GLGizmoRotate::enable_grabber() { m_grabbers[0].enabled = true; } +void GLGizmoRotate::disable_grabber() { m_grabbers[0].enabled = false; } + bool GLGizmoRotate::on_init() { m_grabbers.push_back(Grabber()); + m_grabbers.back().extensions = (GLGizmoBase::EGrabberExtension)(int(GLGizmoBase::EGrabberExtension::PosY) | int(GLGizmoBase::EGrabberExtension::NegY)); return true; } @@ -91,36 +103,33 @@ void GLGizmoRotate::on_start_dragging() m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; } -void GLGizmoRotate::on_update(const UpdateData& data) +void GLGizmoRotate::on_dragging(const UpdateData &data) { - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection())); + const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection())); - Vec2d orig_dir = Vec2d::UnitX(); - Vec2d new_dir = mouse_pos.normalized(); + const Vec2d orig_dir = Vec2d::UnitX(); + const Vec2d new_dir = mouse_pos.normalized(); double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); if (cross2(orig_dir, new_dir) < 0.0) theta = 2.0 * (double)PI - theta; - double len = mouse_pos.norm(); + const double len = mouse_pos.norm(); // snap to coarse snap region - if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius)) - { - double step = 2.0 * (double)PI / (double)SnapRegionsCount; - theta = step * (double)std::round(theta / step); + if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { + const double step = 2.0 * double(PI) / double(SnapRegionsCount); + theta = step * std::round(theta / step); } - else - { + else { // snap to fine snap region (scale) - if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius)) - { - double step = 2.0 * (double)PI / (double)ScaleStepsCount; - theta = step * (double)std::round(theta / step); + if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { + const double step = 2.0 * double(PI) / double(ScaleStepsCount); + theta = step * std::round(theta / step); } } - if (theta == 2.0 * (double)PI) + if (theta == 2.0 * double(PI)) theta = 0.0; m_angle = theta; @@ -128,13 +137,13 @@ void GLGizmoRotate::on_update(const UpdateData& data) void GLGizmoRotate::on_render() { - if (!m_grabbers[0].enabled) + if (!m_grabbers.front().enabled) return; const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); - if (m_hover_id != 0 && !m_grabbers[0].dragging) { + if (m_hover_id != 0 && !m_grabbers.front().dragging) { m_center = m_custom_center == Vec3d::Zero() ? box.center() : m_custom_center; m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; @@ -143,48 +152,47 @@ void GLGizmoRotate::on_render() m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); } + const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); + m_grabbers.front().center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); + m_grabbers.front().angles.z() = m_angle; + m_grabbers.front().color = AXES_COLOR[m_axis]; + m_grabbers.front().hover_color = AXES_HOVER_COLOR[m_axis]; + glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - transform_to_local(selection); + m_grabbers.front().matrix = local_transform(selection); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color.data() : m_highlight_color.data())); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); - render_circle(); + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * m_grabbers.front().matrix; - if (m_hover_id != -1) { - render_scale(); - render_snap_radii(); - render_reference_radius(); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + const bool radius_changed = std::abs(m_old_radius - m_radius) > EPSILON; + m_old_radius = m_radius; + + ColorRGBA color((m_hover_id != -1) ? m_drag_color : m_highlight_color); + render_circle(color, radius_changed); + if (m_hover_id != -1) { + const bool hover_radius_changed = std::abs(m_old_hover_radius - m_radius) > EPSILON; + m_old_hover_radius = m_radius; + + render_scale(color, hover_radius_changed); + render_snap_radii(color, hover_radius_changed); + render_reference_radius(color, hover_radius_changed); + render_angle_arc(m_highlight_color, hover_radius_changed); + } + + render_grabber_connection(color, radius_changed); + shader->stop_using(); } - glsafe(::glColor4fv(m_highlight_color.data())); - - if (m_hover_id != -1) - render_angle(); - render_grabber(box); - render_grabber_extension(box, false); - - glsafe(::glPopMatrix()); -} - -void GLGizmoRotate::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glPushMatrix()); - - transform_to_local(selection); - - const BoundingBoxf3& box = selection.get_bounding_box(); - render_grabbers_for_picking(box); - render_grabber_extension(box, true); - - glsafe(::glPopMatrix()); } //BBS: add input window for move @@ -219,192 +227,226 @@ void GLGizmoRotate3D::load_rotoptimize_state() } } -void GLGizmoRotate::render_circle() const +void GLGizmoRotate::render_circle(const ColorRGBA& color, bool radius_changed) { - ::glBegin(GL_LINE_LOOP); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { - float angle = (float)i * ScaleStepRad; - float x = ::cos(angle) * m_radius; - float y = ::sin(angle) * m_radius; - float z = 0.0f; - ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); + if (!m_circle.is_initialized() || radius_changed) { + m_circle.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(ScaleStepsCount); + init_data.reserve_indices(ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + init_data.add_vertex(Vec3f(::cos(angle) * m_radius, ::sin(angle) * m_radius, 0.0f)); + init_data.add_index(i); + } + + m_circle.init_from(std::move(init_data)); } - glsafe(::glEnd()); + + m_circle.set_color(color); + m_circle.render(); } -void GLGizmoRotate::render_scale() const +void GLGizmoRotate::render_scale(const ColorRGBA& color, bool radius_changed) { - float out_radius_long = m_snap_fine_out_radius; - float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); + const float out_radius_long = m_snap_fine_out_radius; + const float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); - ::glBegin(GL_LINES); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { - float angle = (float)i * ScaleStepRad; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * m_radius; - float in_y = sina * m_radius; - float in_z = 0.0f; - float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; - float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; - float out_z = 0.0f; - ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); - ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); + if (!m_scale.is_initialized() || radius_changed) { + m_scale.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * m_radius; + const float in_y = sina * m_radius; + const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; + const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; + + // vertices + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + + // indices + init_data.add_line(i * 2, i * 2 + 1); + } + + m_scale.init_from(std::move(init_data)); } - glsafe(::glEnd()); + + m_scale.set_color(color); + m_scale.render(); } -void GLGizmoRotate::render_snap_radii() const +void GLGizmoRotate::render_snap_radii(const ColorRGBA& color, bool radius_changed) { - float step = 2.0f * (float)PI / (float)SnapRegionsCount; + const float step = 2.0f * float(PI) / float(SnapRegionsCount); + const float in_radius = m_radius / 3.0f; + const float out_radius = 2.0f * in_radius; - float in_radius = m_radius / 3.0f; - float out_radius = 2.0f * in_radius; + if (!m_snap_radii.is_initialized() || radius_changed) { + m_snap_radii.reset(); - ::glBegin(GL_LINES); - for (unsigned int i = 0; i < SnapRegionsCount; ++i) - { - float angle = (float)i * step; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * in_radius; - float in_y = sina * in_radius; - float in_z = 0.0f; - float out_x = cosa * out_radius; - float out_y = sina * out_radius; - float out_z = 0.0f; - ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); - ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * step); + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * in_radius; + const float in_y = sina * in_radius; + const float out_x = cosa * out_radius; + const float out_y = sina * out_radius; + + // vertices + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + + // indices + init_data.add_line(i * 2, i * 2 + 1); + } + + m_snap_radii.init_from(std::move(init_data)); } - glsafe(::glEnd()); + + m_snap_radii.set_color(color); + m_snap_radii.render(); } -void GLGizmoRotate::render_reference_radius() const +void GLGizmoRotate::render_reference_radius(const ColorRGBA& color, bool radius_changed) { - ::glBegin(GL_LINES); - ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f); - glsafe(::glEnd()); -} + if (!m_reference_radius.is_initialized() || radius_changed) { + m_reference_radius.reset(); -void GLGizmoRotate::render_angle() const -{ - float step_angle = (float)m_angle / AngleResolution; - float ex_radius = m_radius * (1.0f + GrabberOffset); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2); + init_data.reserve_indices(2); - ::glBegin(GL_LINE_STRIP); - for (unsigned int i = 0; i <= AngleResolution; ++i) - { - float angle = (float)i * step_angle; - float x = ::cos(angle) * ex_radius; - float y = ::sin(angle) * ex_radius; - float z = 0.0f; - ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(m_radius * (1.0f + GrabberOffset), 0.0f, 0.0f)); + + // indices + init_data.add_line(0, 1); + + m_reference_radius.init_from(std::move(init_data)); } - glsafe(::glEnd()); + + m_reference_radius.set_color(color); + m_reference_radius.render(); } -void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const +void GLGizmoRotate::render_angle_arc(const ColorRGBA& color, bool radius_changed) { - double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); - m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); - m_grabbers[0].angles(2) = m_angle; - m_grabbers[0].color = AXES_COLOR[m_axis]; - m_grabbers[0].hover_color = AXES_HOVER_COLOR[m_axis]; + const float step_angle = float(m_angle) / float(AngleResolution); + const float ex_radius = m_radius * (1.0f + GrabberOffset); - glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color.data() : m_highlight_color.data())); + const bool angle_changed = std::abs(m_old_angle - m_angle) > EPSILON; + m_old_angle = m_angle; - ::glBegin(GL_LINES); - ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3dv(m_grabbers[0].center.data()); - glsafe(::glEnd()); + if (!m_angle_arc.is_initialized() || radius_changed || angle_changed) { + m_angle_arc.reset(); + if (m_angle > 0.0f) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(1 + AngleResolution); + init_data.reserve_indices(1 + AngleResolution); - m_grabbers[0].color = m_highlight_color; + // vertices + indices + for (unsigned int i = 0; i <= AngleResolution; ++i) { + const float angle = float(i) * step_angle; + init_data.add_vertex(Vec3f(::cos(angle) * ex_radius, ::sin(angle) * ex_radius, 0.0f)); + init_data.add_index(i); + } + + m_angle_arc.init_from(std::move(init_data)); + } + } + + m_angle_arc.set_color(color); + m_angle_arc.render(); +} + +void GLGizmoRotate::render_grabber_connection(const ColorRGBA& color, bool radius_changed) +{ + if (!m_grabber_connection.model.is_initialized() || radius_changed || !m_grabber_connection.old_center.isApprox(m_grabbers.front().center)) { + m_grabber_connection.model.reset(); + m_grabber_connection.old_center = m_grabbers.front().center; + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex((Vec3f)m_grabbers.front().center.cast()); + + // indices + init_data.add_line(0, 1); + + m_grabber_connection.model.init_from(std::move(init_data)); + } + + m_grabber_connection.model.set_color(color); + m_grabber_connection.model.render(); +} + +void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) +{ + m_grabbers.front().color = m_highlight_color; render_grabbers(box); } -void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const +Transform3d GLGizmoRotate::local_transform(const Selection& selection) const { - double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; - //float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); - //double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size); - - std::array color = m_grabbers[0].color; - if (!picking && m_hover_id != -1) { - color = m_grabbers[0].hover_color; - //color[0] = 1.0f - color[0]; - //color[1] = 1.0f - color[1]; - //color[2] = 1.0f - color[2]; - } - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - const_cast(&m_cone)->set_color(-1, color); - if (!picking) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - } - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); - glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - glsafe(::glTranslated(0.0, 0.0, 1.5 * size)); - glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); - m_cone.render(); - glsafe(::glPopMatrix()); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); - glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - glsafe(::glTranslated(0.0, 0.0, 1.5 * size)); - glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); - m_cone.render(); - glsafe(::glPopMatrix()); - - if (! picking) - shader->stop_using(); -} - -void GLGizmoRotate::transform_to_local(const Selection& selection) const -{ - glsafe(::glTranslated(m_center(0), m_center(1), m_center(2))); - - if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) { - Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); - } + Transform3d ret; switch (m_axis) { case X: { - glsafe(::glRotatef(90.0f, 0.0f, 1.0f, 0.0f)); - glsafe(::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f)); + ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); break; } case Y: { - glsafe(::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f)); - glsafe(::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f)); + ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY()); break; } default: case Z: { - // no rotation + ret = Transform3d::Identity(); break; } } + + if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) + ret = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true) * ret; + + return Geometry::assemble_transform(m_center) * ret; } Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const { - double half_pi = 0.5 * (double)PI; + const double half_pi = 0.5 * double(PI); Transform3d m = Transform3d::Identity(); @@ -431,40 +473,75 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons } if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) - m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); + m = m * selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true).inverse(); m.translate(-m_center); - return transform(mouse_ray, m).intersect_plane(0.0); + const Linef3 local_mouse_ray = transform(mouse_ray, m); + if (std::abs(local_mouse_ray.vector().dot(Vec3d::UnitZ())) < EPSILON) { + // if the ray is parallel to the plane containing the circle + if (std::abs(local_mouse_ray.vector().dot(Vec3d::UnitY())) > 1.0 - EPSILON) + // if the ray is parallel to grabber direction + return Vec3d::UnitX(); + else { + const Vec3d world_pos = (local_mouse_ray.a.x() >= 0.0) ? mouse_ray.a - m_center : mouse_ray.b - m_center; + m.translate(m_center); + return m * world_pos; + } + } + else + return local_mouse_ray.intersect_plane(0.0); } //BBS: GUI refactor: add obj manipulation GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation) : GLGizmoBase(parent, icon_filename, sprite_id) + , m_gizmos({ GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) }) //BBS: GUI refactor: add obj manipulation , m_object_manipulation(obj_manipulation) { - m_gizmos.emplace_back(parent, GLGizmoRotate::X); - m_gizmos.emplace_back(parent, GLGizmoRotate::Y); - m_gizmos.emplace_back(parent, GLGizmoRotate::Z); - - for (unsigned int i = 0; i < 3; ++i) { - m_gizmos[i].set_group_id(i); - } - load_rotoptimize_state(); } +bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { + + if (mouse_event.Dragging() && m_dragging) { + // Apply new temporary rotations + TransformationType transformation_type( + TransformationType::World_Relative_Joint); + if (mouse_event.AltDown()) + transformation_type.set_independent(); + m_parent.get_selection().rotate(get_rotation(), transformation_type); + } + return use_grabbers(mouse_event); +} + +void GLGizmoRotate3D::data_changed(bool is_serializing) { + const Selection &selection = m_parent.get_selection(); + bool is_wipe_tower = selection.is_wipe_tower(); + if (is_wipe_tower) { + DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + float wipe_tower_rotation_angle = + dynamic_cast( + config.option("wipe_tower_rotation_angle")) + ->value; + set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle)); + m_gizmos[0].disable_grabber(); + m_gizmos[1].disable_grabber(); + } else { + set_rotation(Vec3d::Zero()); + m_gizmos[0].enable_grabber(); + m_gizmos[1].enable_grabber(); + } +} + bool GLGizmoRotate3D::on_init() { - for (GLGizmoRotate& g : m_gizmos) { - if (!g.init()) - return false; - } + for (GLGizmoRotate& g : m_gizmos) + if (!g.init()) return false; - for (unsigned int i = 0; i < 3; ++i) { + for (unsigned int i = 0; i < 3; ++i) m_gizmos[i].set_highlight_color(AXES_COLOR[i]); - } m_shortcut_key = WXK_CONTROL_R; @@ -485,14 +562,21 @@ bool GLGizmoRotate3D::on_is_activable() const void GLGizmoRotate3D::on_start_dragging() { - if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].start_dragging(); + assert(0 <= m_hover_id && m_hover_id < 3); + m_gizmos[m_hover_id].start_dragging(); } void GLGizmoRotate3D::on_stop_dragging() { - if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].stop_dragging(); + assert(0 <= m_hover_id && m_hover_id < 3); + m_parent.do_rotate(L("Gizmo-Rotate")); + m_gizmos[m_hover_id].stop_dragging(); +} + +void GLGizmoRotate3D::on_dragging(const UpdateData &data) +{ + assert(0 <= m_hover_id && m_hover_id < 3); + m_gizmos[m_hover_id].dragging(data); } void GLGizmoRotate3D::on_render() @@ -509,6 +593,23 @@ void GLGizmoRotate3D::on_render() m_gizmos[Z].render(); } +void GLGizmoRotate3D::on_register_raycasters_for_picking() +{ + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); + for (GLGizmoRotate& g : m_gizmos) { + g.register_raycasters_for_picking(); + } +} + +void GLGizmoRotate3D::on_unregister_raycasters_for_picking() +{ + for (GLGizmoRotate& g : m_gizmos) { + g.unregister_raycasters_for_picking(); + } + m_parent.set_raycaster_gizmos_on_top(false); +} + GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, State & state, const Alignment &alignment) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 9862ec2901..edafc5ae23 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Filip Sykala @Jony01, Tomáš Mészáros @tamasmeszaros +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoRotate_hpp_ #define slic3r_GLGizmoRotate_hpp_ @@ -9,11 +13,10 @@ namespace Slic3r { namespace GUI { - +class Selection; class GLGizmoRotate : public GLGizmoBase { static const float Offset; - static const unsigned int CircleResolution; static const unsigned int AngleResolution; static const unsigned int ScaleStepsCount; static const float ScaleStepRad; @@ -25,27 +28,42 @@ class GLGizmoRotate : public GLGizmoBase public: enum Axis : unsigned char { - X, - Y, - Z + X=0, + Y=1, + Z=2 }; private: Axis m_axis; - double m_angle; + double m_angle{ 0.0 }; + Vec3d m_custom_center{Vec3d::Zero()}; + Vec3d m_center{ Vec3d::Zero() }; + float m_radius{ 0.0f }; + float m_snap_coarse_in_radius{ 0.0f }; + float m_snap_coarse_out_radius{ 0.0f }; + float m_snap_fine_in_radius{ 0.0f }; + float m_snap_fine_out_radius{ 0.0f }; + + ColorRGBA m_drag_color; + ColorRGBA m_highlight_color; - mutable Vec3d m_custom_center{Vec3d::Zero()}; - mutable Vec3d m_center; - mutable float m_radius; - - mutable float m_snap_coarse_in_radius; - mutable float m_snap_coarse_out_radius; - mutable float m_snap_fine_in_radius; - mutable float m_snap_fine_out_radius; + GLModel m_circle; + GLModel m_scale; + GLModel m_snap_radii; + GLModel m_reference_radius; + GLModel m_angle_arc; + struct GrabberConnection + { + GLModel model; + Vec3d old_center{ Vec3d::Zero() }; + }; + GrabberConnection m_grabber_connection; + float m_old_radius{ 0.0f }; + float m_old_hover_radius{ 0.0f }; + float m_old_angle{ 0.0f }; public: GLGizmoRotate(GLCanvas3D& parent, Axis axis); - GLGizmoRotate(const GLGizmoRotate& other); virtual ~GLGizmoRotate() = default; double get_angle() const { return m_angle; } @@ -55,24 +73,41 @@ public: void set_center(const Vec3d &point) { m_custom_center = point; } + void start_dragging(); + void stop_dragging(); + + void enable_grabber(); + void disable_grabber(); + + void set_highlight_color(const ColorRGBA &color); + + /// + /// Postpone to Grabber for move + /// Detect move of object by dragging + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + void dragging(const UpdateData &data); + protected: bool on_init() override; std::string on_get_name() const override { return ""; } void on_start_dragging() override; - void on_update(const UpdateData& data) override; + void on_dragging(const UpdateData &data) override; void on_render() override; - void on_render_for_picking() override; private: - void render_circle() const; - void render_scale() const; - void render_snap_radii() const; - void render_reference_radius() const; - void render_angle() const; - void render_grabber(const BoundingBoxf3& box) const; - void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; + void render_circle(const ColorRGBA& color, bool radius_changed); + void render_scale(const ColorRGBA& color, bool radius_changed); + void render_snap_radii(const ColorRGBA& color, bool radius_changed); + void render_reference_radius(const ColorRGBA& color, bool radius_changed); + void render_angle_arc(const ColorRGBA& color, bool radius_changed); + void render_grabber_connection(const ColorRGBA& color, bool radius_changed); + void render_grabber(const BoundingBoxf3& box); + + Transform3d local_transform(const Selection& selection) const; - void transform_to_local(const Selection& selection) const; // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const; }; @@ -81,7 +116,7 @@ class GLGizmoRotate3D : public GLGizmoBase { // BBS: change to protected for subclass access protected: - std::vector m_gizmos; + std::array m_gizmos; //BBS: add size adjust related GizmoObjectManipulation* m_object_manipulation; @@ -92,10 +127,9 @@ public: GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation); Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } - void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } + void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); } - std::string get_tooltip() const override - { + std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); if (tooltip.empty()) tooltip = m_gizmos[Y].get_tooltip(); @@ -111,54 +145,44 @@ public: m_gizmos[Z].set_center(point); } + /// + /// Postpone to Rotation + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + void data_changed(bool is_serializing) override; protected: bool on_init() override; std::string on_get_name() const override; - void on_set_state() override - { + void on_set_state() override { for (GLGizmoRotate& g : m_gizmos) g.set_state(m_state); } - void on_set_hover_id() override - { + void on_set_hover_id() override { for (int i = 0; i < 3; ++i) m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } - void on_enable_grabber(unsigned int id) override - { - if (id < 3) - m_gizmos[id].enable_grabber(0); - } - void on_disable_grabber(unsigned int id) override - { - if (id < 3) - m_gizmos[id].disable_grabber(0); - } + bool on_is_activable() const override; void on_start_dragging() override; void on_stop_dragging() override; - void on_update(const UpdateData& data) override - { - for (GLGizmoRotate& g : m_gizmos) { - g.update(data); - } - } + void on_dragging(const UpdateData &data) override; + void on_render() override; - void on_render_for_picking() override - { - for (GLGizmoRotate& g : m_gizmos) { - g.render_for_picking(); - } - } + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; private: - class RotoptimzeWindow { + class RotoptimzeWindow + { ImGuiWrapper *m_imgui = nullptr; - public: + public: struct State { float accuracy = 1.f; int method_id = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 0f98c5dd35..93fbf7d9b3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -1,7 +1,11 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoScale.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" #include @@ -24,12 +28,19 @@ Vec3d GetIntersectionOfRayAndPlane(Vec3d ray_position, Vec3d ray_dir, Vec3d plan //BBS: GUI refactor: add obj manipulation GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation) : GLGizmoBase(parent, icon_filename, sprite_id) - , m_scale(Vec3d::Ones()) - , m_offset(Vec3d::Zero()) - , m_snap_step(0.05) //BBS: GUI refactor: add obj manipulation , m_object_manipulation(obj_manipulation) + , m_base_color(DEFAULT_BASE_COLOR) + , m_drag_color(DEFAULT_DRAG_COLOR) + , m_highlight_color(DEFAULT_HIGHLIGHT_COLOR) { + m_grabber_connections[0].grabber_indices = { 0, 1 }; + m_grabber_connections[1].grabber_indices = { 2, 3 }; + m_grabber_connections[2].grabber_indices = { 4, 5 }; + m_grabber_connections[3].grabber_indices = { 6, 7 }; + m_grabber_connections[4].grabber_indices = { 7, 8 }; + m_grabber_connections[5].grabber_indices = { 8, 9 }; + m_grabber_connections[6].grabber_indices = { 9, 6 }; } std::string GLGizmoScale3D::get_tooltip() const @@ -41,22 +52,22 @@ std::string GLGizmoScale3D::get_tooltip() const Vec3f scale = 100.0f * Vec3f::Ones(); if (single_instance) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast(); + scale = 100.0f * selection.get_first_volume()->get_instance_scaling_factor().cast(); else if (single_volume) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); + scale = 100.0f * selection.get_first_volume()->get_volume_scaling_factor().cast(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) - return "X: " + format(scale(0), 4) + "%"; + return "X: " + format(scale.x(), 4) + "%"; else if (m_hover_id == 2 || m_hover_id == 3 || m_grabbers[2].dragging || m_grabbers[3].dragging) - return "Y: " + format(scale(1), 4) + "%"; + return "Y: " + format(scale.y(), 4) + "%"; else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging) - return "Z: " + format(scale(2), 4) + "%"; + return "Z: " + format(scale.z(), 4) + "%"; else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) { - std::string tooltip = "X: " + format(scale(0), 2) + "%\n"; - tooltip += "Y: " + format(scale(1), 2) + "%\n"; - tooltip += "Z: " + format(scale(2), 2) + "%"; + std::string tooltip = "X: " + format(scale.x(), 2) + "%\n"; + tooltip += "Y: " + format(scale.y(), 2) + "%\n"; + tooltip += "Z: " + format(scale.z(), 2) + "%"; return tooltip; } else @@ -69,10 +80,49 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable) m_grabbers[i].enabled = enable; } +bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Dragging()) { + if (m_dragging) { + // Apply new temporary scale factors + TransformationType transformation_type(TransformationType::Local_Absolute_Joint); + if (mouse_event.AltDown()) + transformation_type.set_independent(); + + Selection& selection = m_parent.get_selection(); + selection.scale(m_scale, transformation_type); + if (mouse_event.CmdDown()) selection.translate(m_offset, true); + } + } + return use_grabbers(mouse_event); +} + +void GLGizmoScale3D::data_changed(bool is_serializing) { + const Selection &selection = m_parent.get_selection(); + bool enable_scale_xyz = selection.is_single_full_instance() || + selection.is_single_volume() || + selection.is_single_modifier(); + for (unsigned int i = 0; i < 6; ++i) + m_grabbers[i].enabled = enable_scale_xyz; + + if (enable_scale_xyz) { + // all volumes in the selection belongs to the same instance, any of + // them contains the needed data, so we take the first + const GLVolume *volume = selection.get_first_volume(); + if (selection.is_single_full_instance()) { + set_scale(volume->get_instance_scaling_factor()); + } else if (selection.is_single_volume() || + selection.is_single_modifier()) { + set_scale(volume->get_volume_scaling_factor()); + } + } else { + set_scale(Vec3d::Ones()); + } +} + bool GLGizmoScale3D::on_init() { - for (int i = 0; i < 10; ++i) - { + for (int i = 0; i < 10; ++i) { m_grabbers.push_back(Grabber()); } @@ -106,31 +156,33 @@ bool GLGizmoScale3D::on_is_activable() const void GLGizmoScale3D::on_start_dragging() { - if (m_hover_id != -1) - { - m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.plane_center = m_grabbers[4].center; - m_starting.plane_nromal = m_grabbers[5].center - m_grabbers[4].center; - m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); - m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); + assert(m_hover_id != -1); + m_starting.drag_position = m_grabbers[m_hover_id].center; + m_starting.plane_center = m_grabbers[4].center; + m_starting.plane_nromal = m_grabbers[5].center - m_grabbers[4].center; + m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); + m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); - const Vec3d& center = m_starting.box.center(); - m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2)); - m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min(0), center(1), center(2)); - m_starting.pivots[2] = m_transform * Vec3d(center(0), m_starting.box.max(1), center(2)); - m_starting.pivots[3] = m_transform * Vec3d(center(0), m_starting.box.min(1), center(2)); - m_starting.pivots[4] = m_transform * Vec3d(center(0), center(1), m_starting.box.max(2)); - m_starting.pivots[5] = m_transform * Vec3d(center(0), center(1), m_starting.box.min(2)); - } + const Vec3d& center = m_starting.box.center(); + m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); } -void GLGizmoScale3D::on_update(const UpdateData& data) +void GLGizmoScale3D::on_stop_dragging() { + m_parent.do_scale(L("Gizmo-Scale")); +} + +void GLGizmoScale3D::on_dragging(const UpdateData& data) { - if ((m_hover_id == 0) || (m_hover_id == 1)) + if (m_hover_id == 0 || m_hover_id == 1) do_scale_along_axis(X, data); - else if ((m_hover_id == 2) || (m_hover_id == 3)) + else if (m_hover_id == 2 || m_hover_id == 3) do_scale_along_axis(Y, data); - else if ((m_hover_id == 4) || (m_hover_id == 5)) + else if (m_hover_id == 4 || m_hover_id == 5) do_scale_along_axis(Z, data); else if (m_hover_id >= 6) do_scale_uniform(data); @@ -162,7 +214,7 @@ void GLGizmoScale3D::on_render() } // gets transform from first selected volume - const GLVolume* v = selection.get_volume(*idxs.begin()); + const GLVolume* v = selection.get_first_volume(); m_transform = v->get_instance_transformation().get_matrix(); // gets angles from first selected volume angles = v->get_instance_rotation(); @@ -171,7 +223,7 @@ void GLGizmoScale3D::on_render() m_offsets_transform = offsets_transform; } else if (single_volume) { - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); m_box = v->bounding_box(); m_transform = v->world_matrix(); angles = Geometry::extract_euler_angles(m_transform); @@ -183,31 +235,31 @@ void GLGizmoScale3D::on_render() m_box = selection.get_bounding_box(); const Vec3d& center = m_box.center(); - Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); - Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); - Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); + const Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); + const Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); + const Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); - bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); + const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); // x axis - m_grabbers[0].center = m_transform * Vec3d(m_box.min(0), center(1), m_box.min(2)); - m_grabbers[1].center = m_transform * Vec3d(m_box.max(0), center(1), m_box.min(2)); + m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), m_box.min.z()); + m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), m_box.min.z()); // y axis - m_grabbers[2].center = m_transform * Vec3d(center(0), m_box.min(1), m_box.min(2)); - m_grabbers[3].center = m_transform * Vec3d(center(0), m_box.max(1), m_box.min(2)); + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y(), m_box.min.z()); + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y(), m_box.min.z()); // z axis do not show 4 - m_grabbers[4].center = m_transform * Vec3d(center(0), center(1), m_box.min(2)); + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z()); m_grabbers[4].enabled = false; - m_grabbers[5].center = m_transform * Vec3d(center(0), center(1), m_box.max(2)); + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z()); // uniform - m_grabbers[6].center = m_transform * Vec3d(m_box.min(0), m_box.min(1), m_box.min(2)); - m_grabbers[7].center = m_transform * Vec3d(m_box.max(0), m_box.min(1), m_box.min(2)); - m_grabbers[8].center = m_transform * Vec3d(m_box.max(0), m_box.max(1), m_box.min(2)); - m_grabbers[9].center = m_transform * Vec3d(m_box.min(0), m_box.max(1), m_box.min(2)); + m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), m_box.min.z()); + m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), m_box.min.z()); + m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), m_box.min.z()); + m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), m_box.min.z()); for (int i = 0; i < 6; ++i) { m_grabbers[i].color = AXES_COLOR[i/2]; @@ -228,51 +280,90 @@ void GLGizmoScale3D::on_render() const BoundingBoxf3& selection_box = selection.get_bounding_box(); - float grabber_mean_size = (float)((selection_box.size()(0) + selection_box.size()(1) + selection_box.size()(2)) / 3.0); + const float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0); - //draw connections - - // BBS: when select multiple objects, uniform scale can be deselected, display the connection(4,5) - //if (single_instance || single_volume) { - - if (m_grabbers[4].enabled && m_grabbers[5].enabled) { - glsafe(::glColor4fv(m_grabbers[4].color.data())); - render_grabbers_connection(4, 5); + //draw connections + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + // BBS: when select multiple objects, uniform scale can be deselected, display the connection(4,5) + //if (single_instance || single_volume) { + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + if (m_grabbers[4].enabled && m_grabbers[5].enabled) + render_grabbers_connection(4, 5, m_grabbers[4].color); + render_grabbers_connection(6, 7, m_grabbers[2].color); + render_grabbers_connection(7, 8, m_grabbers[2].color); + render_grabbers_connection(8, 9, m_grabbers[0].color); + render_grabbers_connection(9, 6, m_grabbers[0].color); + shader->stop_using(); } - glsafe(::glColor4fv(m_grabbers[2].color.data())); - render_grabbers_connection(6, 7); - render_grabbers_connection(8, 9); - - glsafe(::glColor4fv(m_grabbers[0].color.data())); - render_grabbers_connection(7, 8); - render_grabbers_connection(9, 6); - // draw grabbers - render_grabbers(grabber_mean_size); -} - -void GLGizmoScale3D::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); -} - -void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const -{ - unsigned int grabbers_count = (unsigned int)m_grabbers.size(); - if ((id_1 < grabbers_count) && (id_2 < grabbers_count)) - { - glLineStipple(1, 0x0FFF); - glEnable(GL_LINE_STIPPLE); - ::glBegin(GL_LINES); - ::glVertex3dv(m_grabbers[id_1].center.data()); - ::glVertex3dv(m_grabbers[id_2].center.data()); - glsafe(::glEnd()); - glDisable(GL_LINE_STIPPLE); + shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + render_grabbers(grabber_mean_size); + shader->stop_using(); } } +void GLGizmoScale3D::on_register_raycasters_for_picking() +{ + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); +} + +void GLGizmoScale3D::on_unregister_raycasters_for_picking() +{ + m_parent.set_raycaster_gizmos_on_top(false); +} + +void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color) +{ + auto grabber_connection = [this](unsigned int id_1, unsigned int id_2) { + for (int i = 0; i < int(m_grabber_connections.size()); ++i) { + if (m_grabber_connections[i].grabber_indices.first == id_1 && m_grabber_connections[i].grabber_indices.second == id_2) + return i; + } + return -1; + }; + + const int id = grabber_connection(id_1, id_2); + if (id == -1) + return; + + if (!m_grabber_connections[id].model.is_initialized() || + !m_grabber_connections[id].old_v1.isApprox(m_grabbers[id_1].center) || + !m_grabber_connections[id].old_v2.isApprox(m_grabbers[id_2].center)) { + m_grabber_connections[id].old_v1 = m_grabbers[id_1].center; + m_grabber_connections[id].old_v2 = m_grabbers[id_2].center; + m_grabber_connections[id].model.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)m_grabbers[id_1].center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[id_2].center.cast()); + + // indices + init_data.add_line(0, 1); + + m_grabber_connections[id].model.init_from(std::move(init_data)); + } + + m_grabber_connections[id].model.set_color(color); + glLineStipple(1, 0x0FFF); + glEnable(GL_LINE_STIPPLE); + m_grabber_connections[id].model.render(); + glDisable(GL_LINE_STIPPLE); +} + //BBS: add input window for move void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit) { @@ -283,11 +374,9 @@ void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { double ratio = calc_ratio(data); - if (ratio > 0.0) - { + if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; - if (m_starting.ctrl_down) - { + if (m_starting.ctrl_down) { double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); if (m_hover_id == 2 * axis) local_offset *= -1.0; @@ -308,11 +397,10 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) } } -void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) +void GLGizmoScale3D::do_scale_uniform(const UpdateData & data) { - double ratio = calc_ratio(data); - if (ratio > 0.0) - { + const double ratio = calc_ratio(data); + if (ratio > 0.0) { m_scale = m_starting.scale * ratio; m_offset = Vec3d::Zero(); } @@ -322,11 +410,11 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.plane_center; + Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.plane_center; + Vec3d starting_vec = m_starting.drag_position - pivot; double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { + if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); Vec3d plane_normal = m_starting.plane_nromal; if (m_hover_id == 5) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 839c7f6823..589b074491 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoScale_hpp_ #define slic3r_GLGizmoScale_hpp_ @@ -28,15 +32,28 @@ class GLGizmoScale3D : public GLGizmoBase StartingData() : scale(Vec3d::Ones()), drag_position(Vec3d::Zero()), ctrl_down(false) { for (int i = 0; i < 5; ++i) { pivots[i] = Vec3d::Zero(); } } }; - mutable BoundingBoxf3 m_box; - mutable Transform3d m_transform; + BoundingBoxf3 m_box; + Transform3d m_transform; // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) - mutable Transform3d m_offsets_transform; - Vec3d m_scale; - Vec3d m_offset; - double m_snap_step; + Transform3d m_offsets_transform; + Vec3d m_scale{ Vec3d::Ones() }; + Vec3d m_offset{ Vec3d::Zero() }; + double m_snap_step{ 0.05 }; StartingData m_starting; + ColorRGBA m_base_color; + ColorRGBA m_drag_color; + ColorRGBA m_highlight_color; + + struct GrabberConnection + { + GLModel model; + std::pair grabber_indices; + Vec3d old_v1{ Vec3d::Zero() }; + Vec3d old_v2{ Vec3d::Zero() }; + }; + std::array m_grabber_connections; + //BBS: add size adjust related GizmoObjectManipulation* m_object_manipulation; @@ -51,24 +68,32 @@ public: const Vec3d& get_scale() const { return m_scale; } void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } - const Vec3d& get_offset() const { return m_offset; } - std::string get_tooltip() const override; + /// + /// Postpone to Grabber for scale + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + void data_changed(bool is_serializing) override; void enable_ununiversal_scale(bool enable); protected: virtual bool on_init() override; virtual std::string on_get_name() const override; virtual bool on_is_activable() const override; virtual void on_start_dragging() override; - virtual void on_update(const UpdateData& data) override; + virtual void on_stop_dragging() override; + virtual void on_dragging(const UpdateData& data) override; virtual void on_render() override; - virtual void on_render_for_picking() override; + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; //BBS: GUI refactor: add object manipulation virtual void on_render_input_window(float x, float y, float bottom_limit); private: - void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; + void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color); void do_scale_along_axis(Axis axis, const UpdateData& data); void do_scale_uniform(const UpdateData& data); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index d0fae7c033..09e999ec52 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2020 - 2022 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoSeam.hpp" #include "libslic3r/Model.hpp" @@ -62,7 +66,7 @@ std::string GLGizmoSeam::on_get_name() const -void GLGizmoSeam::render_painter_gizmo() const +void GLGizmoSeam::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); @@ -120,8 +124,12 @@ void GLGizmoSeam::render_triangles(const Selection& selection) const if (is_left_handed) glsafe(::glFrontFace(GL_CW)); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg)); Matrix3f normal_matrix = static_cast(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast()); @@ -129,11 +137,10 @@ void GLGizmoSeam::render_triangles(const Selection& selection) const shader->set_uniform("volume_world_matrix", trafo_matrix); shader->set_uniform("volume_mirrored", is_left_handed); shader->set_uniform("slope.actived", m_parent.is_using_slope()); - shader->set_uniform("slope.volume_world_normal_matrix", static_cast(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast())); + shader->set_uniform("slope.volume_world_normal_matrix", normal_matrix); shader->set_uniform("slope.normal_z", normal_z); - m_triangle_selectors[mesh_id]->render(m_imgui); + m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); - glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); } @@ -295,8 +302,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) } else { if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position(-1., false); + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -310,7 +317,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position(clp_dist, true); } + if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } ImGui::Separator(); m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only); @@ -388,7 +395,7 @@ void GLGizmoSeam::update_from_model_object(bool first_update) m_triangle_selectors.clear(); int volume_id = -1; - std::vector> ebt_colors; + std::vector ebt_colors; ebt_colors.push_back(GLVolume::NEUTRAL_COLOR); ebt_colors.push_back(TriangleSelectorGUI::enforcers_color); ebt_colors.push_back(TriangleSelectorGUI::blockers_color); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 2ba58d31ed..2e20f50183 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoSeam_hpp_ #define slic3r_GLGizmoSeam_hpp_ @@ -10,7 +14,7 @@ class GLGizmoSeam : public GLGizmoPainterBase public: GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void render_painter_gizmo() const override; + void render_painter_gizmo() override; //BBS bool on_key_down_select_tool_type(int keyCode); @@ -32,9 +36,9 @@ protected: wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; - std::string get_gizmo_entering_text() const override { return "Entering Seam painting"; } - std::string get_gizmo_leaving_text() const override { return "Leaving Seam painting"; } - std::string get_action_snapshot_name() override { return "Paint-on seam editing"; } + std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); } + std::string get_action_snapshot_name() const override { return _u8L("Paint-on seam editing"); } static const constexpr float CursorRadiusMin = 0.05f; // cannot be zero const float get_cursor_radius_min() const override { return CursorRadiusMin; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 7b7cc2065d..ca1575bba3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2021 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Hejl @hejllukas, Enrico Turri @enricoturri1966, David Kocík @kocikdav, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoSimplify.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" @@ -6,6 +10,7 @@ #include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/OpenGLManager.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/QuadricEdgeCollapse.hpp" @@ -622,7 +627,7 @@ void GLGizmoSimplify::init_model(const indexed_triangle_set& its) m_c->selection_info()->get_active_instance(), m_volume); if (const Selection&sel = m_parent.get_selection(); sel.get_volume_idxs().size() == 1) - m_glmodel.set_color(-1, sel.get_volume(*sel.get_volume_idxs().begin())->color); + m_glmodel.set_color(sel.get_volume(*sel.get_volume_idxs().begin())->color); m_triangle_count = its.indices.size(); } @@ -641,19 +646,28 @@ void GLGizmoSimplify::on_render() return; const Transform3d trafo_matrix = selected_volume->world_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); - - auto *gouraud_shader = wxGetApp().get_shader("gouraud_light"); + auto* gouraud_shader = wxGetApp().get_shader("gouraud_light"); glsafe(::glPushAttrib(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST)); gouraud_shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d view_model_matrix = view_matrix * trafo_matrix; + gouraud_shader->set_uniform("view_model_matrix", view_model_matrix); + gouraud_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + gouraud_shader->set_uniform("view_normal_matrix", view_normal_matrix); m_glmodel.render(); gouraud_shader->stop_using(); if (m_show_wireframe) { auto* contour_shader = wxGetApp().get_shader("mm_contour"); contour_shader->start_using(); + contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); + contour_shader->set_uniform("view_model_matrix", view_model_matrix); + contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const ColorRGBA color = m_glmodel.get_color(); + m_glmodel.set_color(ColorRGBA::WHITE()); glsafe(::glLineWidth(1.0f)); glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); //ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); }); @@ -661,11 +675,11 @@ void GLGizmoSimplify::on_render() //glsafe(::glPolygonOffset(5.0, 5.0)); m_glmodel.render(); glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); + m_glmodel.set_color(color); contour_shader->stop_using(); } glsafe(::glPopAttrib()); - glsafe(::glPopMatrix()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 39093e14d6..dedcea8971 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2021 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, Lukáš Matěna @lukasmatena +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoSimplify_hpp_ #define slic3r_GLGizmoSimplify_hpp_ @@ -37,7 +41,6 @@ protected: // must implement virtual bool on_init() override { return true;}; virtual void on_render() override; - virtual void on_render_for_picking() override{}; CommonGizmosDataID on_get_requirements() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 73ed826fc6..ef46c7d002 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -77,6 +77,13 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S void GLGizmoSlaSupports::on_render() { + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + if (!m_sphere.is_initialized()) + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); + ModelObject* mo = m_c->selection_info()->model_object(); const Selection& selection = m_parent.get_selection(); @@ -101,46 +108,38 @@ void GLGizmoSlaSupports::on_render() glsafe(::glDisable(GL_BLEND)); } - -void GLGizmoSlaSupports::on_render_for_picking() +void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) { - const Selection& selection = m_parent.get_selection(); - //glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} + const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const -{ - size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); - - bool has_points = (cache_size != 0); - bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() + const bool has_points = (cache_size != 0); + const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() && ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); if (! has_points && ! has_holes) return; - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader != nullptr) - shader->start_using(); - ScopeGuard guard([shader]() { - if (shader != nullptr) - shader->stop_using(); - }); + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); - float z_shift = m_c->selection_info()->get_sla_shift(); + const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * vol->get_instance_transformation().get_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, z_shift)); - glsafe(::glMultMatrixd(instance_matrix.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d& projection_matrix = camera.get_projection_matrix(); - std::array render_color; + shader->set_uniform("projection_matrix", projection_matrix); + + ColorRGBA render_color; for (size_t i = 0; i < cache_size; ++i) { const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; - const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; + const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false; if (is_mesh_point_clipped(support_point.pos.cast())) continue; @@ -167,15 +166,13 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) } } - const_cast(&m_cone)->set_color(-1, render_color); - const_cast(&m_sphere)->set_color(-1, render_color); - if (shader && !picking) + m_cone.set_color(render_color); + m_sphere.set_color(render_color); + if (!picking) shader->set_uniform("emission_factor", 0.5f); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(support_point.pos(0), support_point.pos(1), support_point.pos(2))); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + const Transform3d support_matrix = Geometry::assemble_transform(support_point.pos.cast()) * instance_scaling_matrix_inverse; if (vol->is_left_handed()) glFrontFace(GL_CW); @@ -188,75 +185,63 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); - Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); - + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); + const Eigen::AngleAxisd aa(q); const double cone_radius = 0.25; // mm const double cone_height = 0.75; - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale)); - glsafe(::glPushMatrix()); - glsafe(::glRotated(180., 1., 0., 0.)); - glsafe(::glScaled(cone_radius, cone_radius, cone_height)); + const Transform3d model_matrix = instance_matrix * support_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform((cone_height + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(), + Vec3d(PI, 0.0, 0.0), Vec3d(cone_radius, cone_radius, cone_height)); + + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); m_cone.render(); - glsafe(::glPopMatrix()); - glsafe(::glTranslatef(0.f, 0.f, cone_height)); - glsafe(::glPopMatrix()); } - glsafe(::glPushMatrix()); - double radius = (double)support_point.head_front_radius * RenderPointScale; - glsafe(::glScaled(radius, radius, radius)); + const double radius = (double)support_point.head_front_radius * RenderPointScale; + const Transform3d model_matrix = instance_matrix * support_matrix * + Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), radius * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); m_sphere.render(); - glsafe(::glPopMatrix()); if (vol->is_left_handed()) glFrontFace(GL_CCW); - - glsafe(::glPopMatrix()); } // Now render the drain holes: if (has_holes && ! picking) { - render_color[0] = 0.7f; - render_color[1] = 0.7f; - render_color[2] = 0.7f; - render_color[3] = 0.7f; - const_cast(&m_cylinder)->set_color(-1, render_color); - if (shader) - shader->set_uniform("emission_factor", 0.5f); + render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; + m_cylinder.set_color(render_color); + shader->set_uniform("emission_factor", 0.5f); for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { if (is_mesh_point_clipped(drain_hole.pos.cast())) continue; - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; if (vol->is_left_handed()) glFrontFace(GL_CW); // Matrices set, we can render the point mark now. - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + const Eigen::AngleAxisd aa(q); + const Transform3d model_matrix = instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); + + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); m_cylinder.render(); - glsafe(::glPopMatrix()); if (vol->is_left_handed()) glFrontFace(GL_CCW); - glsafe(::glPopMatrix()); } } - - glsafe(::glPopMatrix()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 8dd76336a3..e4e7b8241f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -75,9 +75,8 @@ private: bool on_init() override; void on_update(const UpdateData& data) override; void on_render() override; - void on_render_for_picking() override; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, bool picking = false); bool unsaved_changes() const; bool m_lock_unique_islands = false; @@ -91,6 +90,10 @@ private: std::vector m_normal_cache; // to restore after discarding changes or undo/redo ObjectID m_old_mo_id; + GLModel m_cone; + GLModel m_cylinder; + GLModel m_sphere; + // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp index 8726d73146..0d8461395f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp @@ -145,6 +145,8 @@ bool GLGizmoText::on_init() m_scale = m_imgui->get_font_size(); m_shortcut_key = WXK_CONTROL_T; + m_grabbers.push_back(Grabber()); + reset_text_info(); m_desc["font"] = _L("Font"); @@ -161,7 +163,6 @@ bool GLGizmoText::on_init() m_desc["rotate_text_caption"] = _L("Shift + Mouse move up or dowm"); m_desc["rotate_text"] = _L("Rotate text"); - m_grabbers.push_back(Grabber()); return true; } @@ -263,13 +264,8 @@ bool GLGizmoText::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_posit m_mouse_position = mouse_position; } else if (action == SLAGizmoEventType::LeftDown) { - if (!selection.is_empty() && get_hover_id() != -1) { - start_dragging(); - return true; - } - if (m_is_modify) - return true; + return false; Plater *plater = wxGetApp().plater(); if (!plater) @@ -298,7 +294,7 @@ bool GLGizmoText::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_posit // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { - MeshRaycaster mesh_raycaster = MeshRaycaster(mo->volumes[mesh_id]->mesh()); + MeshRaycaster mesh_raycaster = MeshRaycaster(mo->volumes[mesh_id]->mesh_ptr()); if (mesh_raycaster.unproject_on_mesh(mouse_position, trafo_matrices[mesh_id], camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), &facet)) { @@ -330,6 +326,40 @@ bool GLGizmoText::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_posit return true; } +bool GLGizmoText::on_mouse(const wxMouseEvent &mouse_event) +{ + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + bool control_down = mouse_event.CmdDown(); + + if (mouse_event.Moving()) { + gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + } + + // when control is down we allow scene pan and rotation even when clicking + // over some object + bool grabber_contains_mouse = (get_hover_id() != -1); + + if (mouse_event.LeftDown()) { + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + // the gizmo got the event and took some action, there is no need + // to do anything more + return true; + } + + return use_grabbers(mouse_event); +} + +void GLGizmoText::on_register_raycasters_for_picking() +{ + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); +} + +void GLGizmoText::on_unregister_raycasters_for_picking() { m_parent.set_raycaster_gizmos_on_top(false); } + void GLGizmoText::on_set_state() { if (m_state == EState::On) { @@ -456,9 +486,15 @@ void GLGizmoText::on_render() m_grabbers[0].center = m_mouse_position_world; m_grabbers[0].enabled = true; - std::array color = picking_color_component(0); - m_grabbers[0].color = color; - m_grabbers[0].render_for_picking(mean_size); + + GLShaderProgram *shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + render_grabbers(mean_size); + + shader->stop_using(); + } } delete_temp_preview_text_volume(); @@ -470,45 +506,7 @@ void GLGizmoText::on_render() plater->update(); } -void GLGizmoText::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - - int obejct_idx, volume_idx; - ModelVolume *model_volume = get_selected_single_volume(obejct_idx, volume_idx); - if (model_volume && !model_volume->get_text_info().m_text.empty()) { - if (m_grabbers.size() == 1) { - ModelObject *mo = m_c->selection_info()->model_object(); - if (m_is_modify) { - const Selection &selection = m_parent.get_selection(); - mo = selection.get_model()->objects[m_object_idx]; - } - if (mo == nullptr) return; - - const Selection & selection = m_parent.get_selection(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - for (const ModelVolume *mv : mo->volumes) { - if (mv->is_model_part()) { - trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); - } - } - - m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); - - float mean_size = (float) (GLGizmoBase::Grabber::FixedGrabberSize); - m_grabbers[0].center = m_mouse_position_world; - m_grabbers[0].enabled = true; - std::array color = picking_color_component(0); - m_grabbers[0].color = color; - m_grabbers[0].render_for_picking(mean_size); - } - } -} - -void GLGizmoText::on_update(const UpdateData &data) +void GLGizmoText::on_dragging(const UpdateData &data) { Vec2d mouse_pos = Vec2d(data.mouse_pos.x(), data.mouse_pos.y()); const ModelObject *mo = m_c->selection_info()->model_object(); @@ -540,7 +538,7 @@ void GLGizmoText::on_update(const UpdateData &data) if (mesh_id == m_volume_idx) continue; - MeshRaycaster mesh_raycaster = MeshRaycaster(mo->volumes[mesh_id]->mesh()); + MeshRaycaster mesh_raycaster = MeshRaycaster(mo->volumes[mesh_id]->mesh_ptr()); if (mesh_raycaster.unproject_on_mesh(mouse_pos, trafo_matrices[mesh_id], camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), &facet)) { @@ -647,7 +645,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) const Selection &selection = m_parent.get_selection(); if (selection.is_single_full_instance() || selection.is_single_full_object()) { - const GLVolume * gl_volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume * gl_volume = selection.get_first_volume(); int object_idx = gl_volume->object_idx(); if (object_idx != m_object_idx || (object_idx == m_object_idx && m_volume_idx != -1)) { m_object_idx = object_idx; @@ -912,7 +910,7 @@ ModelVolume *GLGizmoText::get_selected_single_volume(int &out_object_idx, int &o { if (m_parent.get_selection().is_single_volume() || m_parent.get_selection().is_single_modifier()) { const Selection &selection = m_parent.get_selection(); - const GLVolume * gl_volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume * gl_volume = selection.get_first_volume(); out_object_idx = gl_volume->object_idx(); ModelObject *model_object = selection.get_model()->objects[out_object_idx]; out_volume_idx = gl_volume->volume_idx(); @@ -938,6 +936,7 @@ void GLGizmoText::reset_text_info() m_keep_horizontal = false; m_is_modify = false; + m_grabbers[0].enabled = false; } bool GLGizmoText::update_text_positions(const std::vector& texts) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp index 53b792364f..e187c72211 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp @@ -83,6 +83,8 @@ public: bool gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down); + bool on_mouse(const wxMouseEvent &mouse_event) override; + bool is_mesh_point_clipped(const Vec3d &point, const Transform3d &trafo) const; BoundingBoxf3 bounding_box() const; @@ -91,8 +93,7 @@ protected: virtual std::string on_get_name() const override; virtual bool on_is_activable() const override; virtual void on_render() override; - virtual void on_render_for_picking() override; - virtual void on_update(const UpdateData &data) override; + virtual void on_dragging(const UpdateData &data) override; void push_combo_style(const float scale); void pop_combo_style(); void push_button_style(bool pressed); @@ -100,6 +101,8 @@ protected: virtual void on_set_state() override; virtual CommonGizmosDataID on_get_requirements() const override; virtual void on_render_input_window(float x, float y, float bottom_limit); + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; void show_tooltip_information(float x, float y); diff --git a/src/slic3r/GUI/Gizmos/GLGizmos.hpp b/src/slic3r/GUI/Gizmos/GLGizmos.hpp index 9751c37232..36435919b7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmos.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmos.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2021 Lukáš Hejl @hejllukas, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmos_hpp_ #define slic3r_GLGizmos_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 4e29d10afb..709cd47257 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2020 - 2023 Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmosCommon.hpp" #include @@ -23,10 +27,10 @@ CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas) using c = CommonGizmosDataID; m_data[c::SelectionInfo].reset( new SelectionInfo(this)); m_data[c::InstancesHider].reset( new InstancesHider(this)); - m_data[c::HollowedMesh].reset( new HollowedMesh(this)); +// m_data[c::HollowedMesh].reset( new HollowedMesh(this)); m_data[c::Raycaster].reset( new Raycaster(this)); m_data[c::ObjectClipper].reset( new ObjectClipper(this)); - m_data[c::SupportsClipper].reset( new SupportsClipper(this)); + // m_data[c::SupportsClipper].reset( new SupportsClipper(this)); } @@ -59,13 +63,6 @@ InstancesHider* CommonGizmosDataPool::instances_hider() const return inst_hider->is_valid() ? inst_hider : nullptr; } -HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const -{ - HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); - assert(hol_mesh); - return hol_mesh->is_valid() ? hol_mesh : nullptr; -} - Raycaster* CommonGizmosDataPool::raycaster() const { Raycaster* rc = dynamic_cast(m_data.at(CommonGizmosDataID::Raycaster).get()); @@ -81,13 +78,6 @@ ObjectClipper* CommonGizmosDataPool::object_clipper() const return (oc && oc->is_valid()) ? oc : nullptr; } -SupportsClipper* CommonGizmosDataPool::supports_clipper() const -{ - SupportsClipper* sc = dynamic_cast(m_data.at(CommonGizmosDataID::SupportsClipper).get()); - assert(sc); - return sc->is_valid() ? sc : nullptr; -} - #ifndef NDEBUG // Check the required resources one by one and return true if all // dependencies are met. @@ -117,12 +107,13 @@ bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const void SelectionInfo::on_update() { const Selection& selection = get_pool()->get_canvas()->get_selection(); + + m_model_object = nullptr; + if (selection.is_single_full_instance()) { m_model_object = selection.get_model()->objects[selection.get_object_idx()]; - m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); + m_z_shift = selection.get_first_volume()->get_sla_shift_z(); } - else - m_model_object = nullptr; } void SelectionInfo::on_release() @@ -132,8 +123,7 @@ void SelectionInfo::on_release() int SelectionInfo::get_active_instance() const { - const Selection& selection = get_pool()->get_canvas()->get_selection(); - return selection.get_instance_idx(); + return get_pool()->get_canvas()->get_selection().get_instance_idx(); } @@ -154,7 +144,7 @@ void InstancesHider::on_update() if (mo && active_inst != -1) { canvas->toggle_model_objects_visibility(false); canvas->toggle_model_objects_visibility(true, mo, active_inst); - canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); + canvas->toggle_sla_auxiliaries_visibility(false, mo, active_inst); canvas->set_use_clipping_planes(true); // Some objects may be sinking, do not show whatever is below the bed. canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), z_min)); @@ -170,7 +160,7 @@ void InstancesHider::on_update() for (const TriangleMesh* mesh : meshes) { m_clippers.emplace_back(new MeshClipper); m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), z_min)); - m_clippers.back()->set_mesh(*mesh); + m_clippers.back()->set_mesh(mesh->its); } m_old_meshes = meshes; } @@ -187,13 +177,6 @@ void InstancesHider::on_release() m_clippers.clear(); } -void InstancesHider::show_supports(bool show) { - if (m_show_supports != show) { - m_show_supports = show; - on_update(); - } -} - void InstancesHider::render_cut() const { const SelectionInfo* sel_info = get_pool()->selection_info(); @@ -214,131 +197,39 @@ void InstancesHider::render_cut() const ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane(); clp.set_normal(-clp.get_normal()); clipper->set_limiting_plane(clp); - } else + } + else clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); - glsafe(::glPushMatrix()); - if (mv->is_model_part()) - glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); - else { - const std::array& c = color_from_model_volume(*mv); - glsafe(::glColor4f(c[0], c[1], c[2], c[3])); - } glsafe(::glPushAttrib(GL_DEPTH_TEST)); glsafe(::glDisable(GL_DEPTH_TEST)); - clipper->render_cut(); + clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); glsafe(::glPopAttrib()); - glsafe(::glPopMatrix()); ++clipper_id; } } - -void HollowedMesh::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; - if (! mo || ! is_sla) - return; - - const GLCanvas3D* canvas = get_pool()->get_canvas(); - const PrintObjects& print_objects = canvas->sla_print()->objects(); - const SLAPrintObject* print_object = m_print_object_idx != -1 - ? print_objects[m_print_object_idx] - : nullptr; - - // Find the respective SLAPrintObject. - if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { - m_print_objects_count = print_objects.size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : print_objects) { - ++m_print_object_idx; - if (po->model_object()->id() == mo->id()) { - print_object = po; - break; - } - } - } - - // If there is a valid SLAPrintObject, check state of Hollowing step. - if (print_object) { - if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { - size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; - if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); - if (! backend_mesh.empty()) { - m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); - Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); - m_hollowed_mesh_transformed->transform(trafo_inv); - m_drainholes = print_object->model_object()->sla_drain_holes; - m_old_hollowing_timestamp = timestamp; - - indexed_triangle_set interior = print_object->hollowed_interior_mesh(); - its_flip_triangles(interior); - m_hollowed_interior_transformed = std::make_unique(std::move(interior)); - m_hollowed_interior_transformed->transform(trafo_inv); - } - else { - m_hollowed_mesh_transformed.reset(nullptr); - } - } - } - else - m_hollowed_mesh_transformed.reset(nullptr); - } -} - - -void HollowedMesh::on_release() -{ - m_hollowed_mesh_transformed.reset(); - m_old_hollowing_timestamp = 0; - m_print_object_idx = -1; -} - - -const TriangleMesh* HollowedMesh::get_hollowed_mesh() const -{ - return m_hollowed_mesh_transformed.get(); -} - -const TriangleMesh* HollowedMesh::get_hollowed_interior() const -{ - return m_hollowed_interior_transformed.get(); -} - - - - void Raycaster::on_update() { wxBusyCursor wait; const ModelObject* mo = get_pool()->selection_info()->model_object(); - if (! mo) + if (mo == nullptr) return; std::vector meshes; const std::vector& mvs = mo->volumes; - if (mvs.size() == 1) { - assert(mvs.front()->is_model_part()); - const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); - if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) - meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); - } - if (meshes.empty()) { - for (const ModelVolume* mv : mvs) { - if (mv->is_model_part()) - meshes.push_back(&mv->mesh()); - } + for (const ModelVolume* mv : mvs) { + if (mv->is_model_part()) + meshes.push_back(&mv->mesh()); } if (meshes != m_old_meshes) { m_raycasters.clear(); for (const TriangleMesh* mesh : meshes) - m_raycasters.emplace_back(new MeshRaycaster(*mesh)); + m_raycasters.emplace_back(new MeshRaycaster(std::make_shared(*mesh))); m_old_meshes = meshes; } } @@ -358,9 +249,6 @@ std::vector Raycaster::raycasters() const } - - - void ObjectClipper::on_update() { const ModelObject* mo = get_pool()->selection_info()->model_object(); @@ -369,24 +257,19 @@ void ObjectClipper::on_update() // which mesh should be cut? std::vector meshes; - bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); - if (has_hollowed) - meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); - - if (meshes.empty()) - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); + std::vector trafos; + for (const ModelVolume* mv : mo->volumes) { + meshes.emplace_back(&mv->mesh()); + trafos.emplace_back(mv->get_transformation()); + } if (meshes != m_old_meshes) { m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_mesh(*mesh); + for (size_t i = 0; i < meshes.size(); ++i) { + m_clippers.emplace_back(new MeshClipper, trafos[i]); + m_clippers.back().first->set_mesh(meshes[i]->its); } - m_old_meshes = meshes; - - if (has_hollowed) - m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); + m_old_meshes = std::move(meshes); m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); @@ -403,48 +286,74 @@ void ObjectClipper::on_release() } -void ObjectClipper::render_cut() const +void ObjectClipper::render_cut(const std::vector* ignore_idxs) const { if (m_clp_ratio == 0.) return; const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - Geometry::Transformation inst_trafo; - bool is_assem_cnv = get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView; - inst_trafo = is_assem_cnv ? - mo->instances[sel_info->get_active_instance()]->get_assemble_transformation() : - mo->instances[sel_info->get_active_instance()]->get_transformation(); - auto offset_to_assembly = mo->instances[0]->get_offset_to_assembly(); + const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation(); + + std::vector ignore_idxs_local = ignore_idxs ? *ignore_idxs : std::vector(); - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - if (is_assem_cnv) { - trafo.set_offset(trafo.get_offset() + offset_to_assembly * (GLVolume::explosion_ratio - 1.0) + vol_trafo.get_offset() * (GLVolume::explosion_ratio - 1.0)); - } - else { - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - } - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - if (is_assem_cnv) - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), std::numeric_limits::max())); - else - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - glsafe(::glPushMatrix()); - // BBS - glsafe(::glColor3f(0.25f, 0.25f, 0.25f)); - clipper->render_cut(); - glsafe(::glPopMatrix()); - - ++clipper_id; + for (auto& clipper : m_clippers) { + Geometry::Transformation trafo = inst_trafo * clipper.second; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + clipper.first->set_plane(*m_clp); + clipper.first->set_transformation(trafo); + clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + // BBS + clipper.first->render_cut({ 0.25f, 0.25f, 0.25f, 1.0f }, &ignore_idxs_local); + clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f }, &ignore_idxs_local); + + // Now update the ignore idxs. Find the first element belonging to the next clipper, + // and remove everything before it and decrement everything by current number of contours. + const int num_of_contours = clipper.first->get_number_of_contours(); + ignore_idxs_local.erase(ignore_idxs_local.begin(), std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) { return idx >= size_t(num_of_contours); } )); + for (size_t& idx : ignore_idxs_local) + idx -= num_of_contours; } } -void ObjectClipper::set_position(double pos, bool keep_normal) +int ObjectClipper::get_number_of_contours() const +{ + int sum = 0; + for (const auto& [clipper, trafo] : m_clippers) + sum += clipper->get_number_of_contours(); + return sum; +} + +int ObjectClipper::is_projection_inside_cut(const Vec3d& point) const +{ + if (m_clp_ratio == 0.) + return -1; + int idx_offset = 0; + for (const auto& [clipper, trafo] : m_clippers) { + if (int idx = clipper->is_projection_inside_cut(point); idx != -1) + return idx_offset + idx; + idx_offset += clipper->get_number_of_contours(); + } + return -1; +} + +bool ObjectClipper::has_valid_contour() const +{ + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); }); +} + +std::vector ObjectClipper::point_per_contour() const +{ + std::vector pts; + + for (const auto& clipper : m_clippers) { + const std::vector pts_clipper = clipper.first->point_per_contour(); + pts.insert(pts.end(), pts_clipper.begin(), pts_clipper.end());; + } + return pts; +} + + +void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) { const ModelObject* mo = get_pool()->selection_info()->model_object(); int active_inst = get_pool()->selection_info()->get_active_instance(); @@ -479,117 +388,26 @@ void ObjectClipper::set_position(double pos, bool keep_normal) get_pool()->get_canvas()->set_as_dirty(); } -void ObjectClipper::set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos) +void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos) { m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset)); m_clp_ratio = pos; get_pool()->get_canvas()->set_as_dirty(); } -bool ObjectClipper::is_projection_inside_cut(const Vec3d &point) const +const ClippingPlane* ObjectClipper::get_clipping_plane(bool ignore_hide_clipped) const { - return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const auto &cl) { - return cl->is_projection_inside_cut(point); - }); + static const ClippingPlane no_clip = ClippingPlane::ClipsNothing(); + return (ignore_hide_clipped || m_hide_clipped) ? m_clp.get() : &no_clip; } -bool ObjectClipper::has_valid_contour() const +void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contour_width) { - return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto &cl) { - return cl->has_valid_contour(); - }); + m_hide_clipped = hide_clipped; + for (auto& clipper : m_clippers) + clipper.first->set_behaviour(fill_cut, contour_width); } -void SupportsClipper::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; - if (! mo || ! is_sla) - return; - - const GLCanvas3D* canvas = get_pool()->get_canvas(); - const PrintObjects& print_objects = canvas->sla_print()->objects(); - const SLAPrintObject* print_object = m_print_object_idx != -1 - ? print_objects[m_print_object_idx] - : nullptr; - - // Find the respective SLAPrintObject. - if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { - m_print_objects_count = print_objects.size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : print_objects) { - ++m_print_object_idx; - if (po->model_object()->id() == mo->id()) { - print_object = po; - break; - } - } - } - - if (print_object - && print_object->is_step_done(slaposSupportTree) - && ! print_object->support_mesh().empty()) - { - // If the supports are already calculated, save the timestamp of the respective step - // so we can later tell they were recalculated. - size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; - if (! m_clipper || timestamp != m_old_timestamp) { - // The timestamp has changed. - m_clipper.reset(new MeshClipper); - // The mesh should already have the shared vertices calculated. - m_clipper->set_mesh(print_object->support_mesh()); - m_old_timestamp = timestamp; - } - } - else - // The supports are not valid. We better dump the cached data. - m_clipper.reset(); -} - - -void SupportsClipper::on_release() -{ - m_clipper.reset(); - m_old_timestamp = 0; - m_print_object_idx = -1; -} - -void SupportsClipper::render_cut() const -{ - const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper(); - if (ocl->get_position() == 0. - || ! get_pool()->instances_hider()->are_supports_shown() - || ! m_clipper) - return; - - const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation(); - Geometry::Transformation trafo = inst_trafo;// * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - - // Get transformation of supports - Geometry::Transformation supports_trafo = trafo; - supports_trafo.set_scaling_factor(Vec3d::Ones()); - supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); - supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); - // I don't know why, but following seems to be correct. - supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2), - 1, - 1.)); - - m_clipper->set_plane(*ocl->get_clipping_plane()); - m_clipper->set_transformation(supports_trafo); - - glsafe(::glPushMatrix()); - glsafe(::glColor3f(1.0f, 0.f, 0.37f)); - m_clipper->render_cut(); - glsafe(::glPopMatrix()); -} - - using namespace AssembleViewDataObjects; AssembleViewDataPool::AssembleViewDataPool(GLCanvas3D* canvas) @@ -696,7 +514,7 @@ void ModelObjectsClipper::on_update() m_clippers.clear(); for (const TriangleMesh* mesh : meshes) { m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_mesh(*mesh); + m_clippers.back()->set_mesh(mesh->its); } m_old_meshes = meshes; @@ -732,11 +550,8 @@ void ModelObjectsClipper::render_cut() const auto& clipper = m_clippers[clipper_id]; clipper->set_plane(*m_clp); clipper->set_transformation(trafo); - glsafe(::glPushMatrix()); // BBS - glsafe(::glColor3f(0.25f, 0.25f, 0.25f)); - clipper->render_cut(); - glsafe(::glPopMatrix()); + clipper->render_cut({0.25f, 0.25f, 0.25f, 1.0f}); ++clipper_id; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index f4211dfb59..6f2138346e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -1,16 +1,22 @@ +///|/ Copyright (c) Prusa Research 2020 - 2023 Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Lukáš Hejl @hejllukas +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GUI_GLGizmosCommon_hpp_ #define slic3r_GUI_GLGizmosCommon_hpp_ #include #include +#include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/MeshUtils.hpp" -#include "libslic3r/SLA/Hollowing.hpp" namespace Slic3r { class ModelObject; - +class ModelInstance; +class SLAPrintObject; +class ModelVolume; namespace GUI { @@ -24,8 +30,12 @@ enum class SLAGizmoEventType : unsigned char { Dragging, Delete, SelectAll, + CtrlDown, + CtrlUp, + ShiftDown, ShiftUp, AltUp, + Escape, ApplyChanges, DiscardChanges, AutomaticGeneration, @@ -66,10 +76,8 @@ enum class CommonGizmosDataID { None = 0, SelectionInfo = 1 << 0, InstancesHider = 1 << 1, - HollowedMesh = 1 << 2, Raycaster = 1 << 3, ObjectClipper = 1 << 4, - SupportsClipper = 1 << 5, }; @@ -79,7 +87,7 @@ enum class CommonGizmosDataID { // by GLGizmoManager, the gizmos keep a pointer to it. class CommonGizmosDataPool { public: - CommonGizmosDataPool(GLCanvas3D* canvas); + explicit CommonGizmosDataPool(GLCanvas3D* canvas); // Update all resources and release what is not used. // Accepts a bitmask of currently required resources. @@ -88,10 +96,10 @@ public: // Getters for the data that need to be accessed from the gizmos directly. CommonGizmosDataObjects::SelectionInfo* selection_info() const; CommonGizmosDataObjects::InstancesHider* instances_hider() const; - CommonGizmosDataObjects::HollowedMesh* hollowed_mesh() const; +// CommonGizmosDataObjects::HollowedMesh* hollowed_mesh() const; CommonGizmosDataObjects::Raycaster* raycaster() const; CommonGizmosDataObjects::ObjectClipper* object_clipper() const; - CommonGizmosDataObjects::SupportsClipper* supports_clipper() const; + // CommonGizmosDataObjects::SupportsClipper* supports_clipper() const; GLCanvas3D* get_canvas() const { return m_canvas; } @@ -140,7 +148,6 @@ protected: virtual void on_update() = 0; CommonGizmosDataPool* get_pool() const { return m_common; } - private: bool m_is_valid = false; CommonGizmosDataPool* m_common = nullptr; @@ -184,8 +191,6 @@ public: CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } #endif // NDEBUG - void show_supports(bool show); - bool are_supports_shown() const { return m_show_supports; } void render_cut() const; protected: @@ -193,42 +198,12 @@ protected: void on_release() override; private: - bool m_show_supports = false; std::vector m_old_meshes; std::vector> m_clippers; }; -class HollowedMesh : public CommonGizmosDataBase -{ -public: - explicit HollowedMesh(CommonGizmosDataPool* cgdp) - : CommonGizmosDataBase(cgdp) {} -#ifndef NDEBUG - CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } -#endif // NDEBUG - - const sla::DrainHoles &get_drainholes() const { return m_drainholes; } - - const TriangleMesh* get_hollowed_mesh() const; - const TriangleMesh* get_hollowed_interior() const; - -protected: - void on_update() override; - void on_release() override; - -private: - std::unique_ptr m_hollowed_mesh_transformed; - std::unique_ptr m_hollowed_interior_transformed; - size_t m_old_hollowing_timestamp = 0; - int m_print_object_idx = -1; - int m_print_objects_count = 0; - sla::DrainHoles m_drainholes; -}; - - - class Raycaster : public CommonGizmosDataBase { public: @@ -260,57 +235,31 @@ public: #ifndef NDEBUG CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } #endif // NDEBUG - - void set_position(double pos, bool keep_normal); double get_position() const { return m_clp_ratio; } - ClippingPlane* get_clipping_plane() const { return m_clp.get(); } - void render_cut() const; + const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const; + void render_cut(const std::vector* ignore_idxs = nullptr) const; + void set_position_by_ratio(double pos, bool keep_normal); + void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); + void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); + + int get_number_of_contours() const; + std::vector point_per_contour() const; - void set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos); - - bool is_projection_inside_cut(const Vec3d &point_in) const; + int is_projection_inside_cut(const Vec3d& point_in) const; bool has_valid_contour() const; + protected: void on_update() override; void on_release() override; private: std::vector m_old_meshes; - std::vector> m_clippers; + std::vector, Geometry::Transformation>> m_clippers; std::unique_ptr m_clp; double m_clp_ratio = 0.; double m_active_inst_bb_radius = 0.; -}; - - - -class SupportsClipper : public CommonGizmosDataBase -{ -public: - explicit SupportsClipper(CommonGizmosDataPool* cgdp) - : CommonGizmosDataBase(cgdp) {} -#ifndef NDEBUG - CommonGizmosDataID get_dependencies() const override { - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::ObjectClipper) - ); - } -#endif // NDEBUG - - void render_cut() const; - - -protected: - void on_update() override; - void on_release() override; - -private: - size_t m_old_timestamp = 0; - int m_print_object_idx = -1; - int m_print_objects_count = 0; - std::unique_ptr m_clipper; + bool m_hide_clipped = true; }; } // namespace CommonGizmosDataObjects diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 87c9a00c9e..3398362855 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -1,3 +1,8 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, David Kocík @kocikdav, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas +///|/ Copyright (c) 2019 John Drake @foxox +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r/libslic3r.h" #include "GLGizmosManager.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" @@ -13,15 +18,15 @@ #include "slic3r/GUI/Gizmos/GLGizmoScale.hpp" #include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" -#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" +//#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" -// BBS -#include "slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp" -#include "slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp" -#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" +//#include "slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp" +//#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp" #include "slic3r/GUI/Gizmos/GLGizmoText.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp" @@ -45,6 +50,7 @@ GLGizmosManager::GLGizmosManager(GLCanvas3D& parent) , m_enabled(false) , m_icons_texture_dirty(true) , m_current(Undefined) + , m_hover(Undefined) , m_tooltip("") , m_serializing(false) //BBS: GUI refactor: add object manipulation in gizmo @@ -55,6 +61,7 @@ GLGizmosManager::GLGizmosManager(GLCanvas3D& parent) std::vector GLGizmosManager::get_selectable_idxs() const { std::vector out; + out.reserve(m_gizmos.size()); if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) { for (size_t i = 0; i < m_gizmos.size(); ++i) if (m_gizmos[i]->get_sprite_id() == (unsigned int)MmuSegmentation) @@ -69,23 +76,23 @@ std::vector GLGizmosManager::get_selectable_idxs() const } //BBS: GUI refactor: GLToolbar&&Gizmo adjust -//when judge the mouse position, {0, 0} is at the left-up corner, and no need to multiply camera_zoom -size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const +GLGizmosManager::EType GLGizmosManager::get_gizmo_from_mouse(const Vec2d &mouse_pos) const { if (! m_enabled) return Undefined; - float cnv_h = (float)m_parent.get_canvas_size().get_height(); - float height = get_scaled_total_height(); + float cnv_h = (float) m_parent.get_canvas_size().get_height(); + float height = get_scaled_total_height(); float icons_size = m_layout.scaled_icons_size(); - float border = m_layout.scaled_border(); + float border = m_layout.scaled_border(); //BBS: GUI refactor: GLToolbar&&Gizmo adjust float cnv_w = (float)m_parent.get_canvas_size().get_width(); float width = get_scaled_total_width(); -#if BBS_TOOLBAR_ON_TOP //float space_width = GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale();; - float top_x = std::max(m_parent.get_main_toolbar_width() + border, 0.5f * (cnv_w - width + m_parent.get_main_toolbar_width() + m_parent.get_collapse_toolbar_width() - m_parent.get_assemble_view_toolbar_width()) + border); + const float separator_width = m_parent.get_separator_toolbar_width(); + float top_x = std::max(0.0f, 0.5f * (cnv_w - (width + separator_width + m_parent.get_main_toolbar_width() - m_parent.get_collapse_toolbar_width() + m_parent.get_assemble_view_toolbar_width()))); + top_x += m_parent.get_main_toolbar_width() + separator_width / 2 + border; if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) top_x = 0.5f * cnv_w + 0.5f * (m_parent.get_assembly_paint_toolbar_width()); float top_y = 0; @@ -102,31 +109,9 @@ size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const if ((float)mouse_pos(0) <= top_x + from_left * stride_x + icons_size) { std::vector selectable = get_selectable_idxs(); if (from_left < selectable.size()) - return selectable[from_left]; + return static_cast(selectable[from_left]); } } -#else - //float top_y = 0.5f * (cnv_h - height) + border; - float top_x = cnv_w - width; - float top_y = 0.5f * (cnv_h - height + m_parent.get_main_toolbar_height() - m_parent.get_assemble_view_toolbar_width()) + border; - float stride_y = m_layout.scaled_stride_y(); - - // is mouse horizontally in the area? - //if ((border <= (float)mouse_pos(0) && ((float)mouse_pos(0) <= border + icons_size))) { - if (((top_x + border) <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= (top_x + border + icons_size))) { - // which icon is it on? - size_t from_top = (size_t)((float)mouse_pos(1) - top_y) / stride_y; - if (from_top < 0) - return Undefined; - // is it really on the icon or already past the border? - if ((float)mouse_pos(1) <= top_y + from_top * stride_y + icons_size) { - std::vector selectable = get_selectable_idxs(); - if (from_top < selectable.size()) - return selectable[from_top]; - } - } -#endif - return Undefined; } @@ -174,6 +159,9 @@ void GLGizmosManager::switch_gizmos_icon_filename() case(EType::MeshBoolean): gizmo->set_icon_filename(m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg"); break; + case(EType::Measure): + gizmo->set_icon_filename(m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg"); + break; } } @@ -204,12 +192,13 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, m_is_dark ? "toolbar_rotate_dark.svg" : "toolbar_rotate.svg", EType::Rotate, &m_object_manipulation)); m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, m_is_dark ? "toolbar_scale_dark.svg" : "toolbar_scale.svg", EType::Scale, &m_object_manipulation)); m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, m_is_dark ? "toolbar_flatten_dark.svg" : "toolbar_flatten.svg", EType::Flatten)); - m_gizmos.emplace_back(new GLGizmoAdvancedCut(m_parent, m_is_dark ? "toolbar_cut_dark.svg" : "toolbar_cut.svg", EType::Cut)); + m_gizmos.emplace_back(new GLGizmoCut3D(m_parent, m_is_dark ? "toolbar_cut_dark.svg" : "toolbar_cut.svg", EType::Cut)); m_gizmos.emplace_back(new GLGizmoMeshBoolean(m_parent, m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg", EType::MeshBoolean)); m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, m_is_dark ? "toolbar_support_dark.svg" : "toolbar_support.svg", EType::FdmSupports)); m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam)); m_gizmos.emplace_back(new GLGizmoText(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Text)); m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmuSegmentation)); + m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure)); m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify)); //m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++)); //m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++)); @@ -289,20 +278,13 @@ float GLGizmosManager::get_layout_scale() return m_layout.scale; } -bool GLGizmosManager::init_arrow(const BackgroundTexture::Metadata& arrow_texture) +bool GLGizmosManager::init_arrow(const std::string& filename) { - if (m_arrow_texture.texture.get_id() != 0) + if (m_arrow_texture.get_id() != 0) return true; - std::string path = resources_dir() + "/images/"; - bool res = false; - - if (!arrow_texture.filename.empty()) - res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000); - if (res) - m_arrow_texture.metadata = arrow_texture; - - return res; + const std::string path = resources_dir() + "/images/"; + return (!filename.empty()) ? m_arrow_texture.load_from_svg_file(path + filename, false, false, false, 1000) : false; } void GLGizmosManager::set_overlay_icon_size(float size) @@ -328,9 +310,9 @@ void GLGizmosManager::refresh_on_off_state() if (m_serializing || m_current == Undefined || m_gizmos.empty()) return; - if (m_current != Undefined - && ! m_gizmos[m_current]->is_activable() && activate_gizmo(Undefined)) - update_data(); + // FS: Why update data after Undefined gizmo activation? + if (!m_gizmos[m_current]->is_activable() && activate_gizmo(Undefined)) + update_data(); } void GLGizmosManager::reset_all_states() @@ -344,9 +326,13 @@ void GLGizmosManager::reset_all_states() bool GLGizmosManager::open_gizmo(EType type) { - int idx = int(type); - if (m_gizmos[idx]->is_activable() - && activate_gizmo(m_current == idx ? Undefined : (EType)idx)) { + int idx = static_cast(type); + + // re-open same type cause closing + if (m_current == type) type = Undefined; + + if (m_gizmos[idx]->is_activable() && activate_gizmo(type)) { + // remove update data into gizmo itself update_data(); #ifdef __WXOSX__ m_parent.post_event(SimpleEvent(wxEVT_PAINT)); @@ -377,27 +363,6 @@ void GLGizmosManager::set_hover_id(int id) m_gizmos[m_current]->set_hover_id(id); } -void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable) -{ - if (!m_enabled || type == Undefined || m_gizmos.empty()) - return; - - if (enable) - m_gizmos[type]->enable_grabber(id); - else - m_gizmos[type]->disable_grabber(id); -} - -void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos) -{ - if (!m_enabled) - return; - - GLGizmoBase* curr = get_current(); - if (curr != nullptr) - curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos)); -} - void GLGizmosManager::update_assemble_view_data() { if (m_assemble_view_data) { @@ -410,70 +375,12 @@ void GLGizmosManager::update_assemble_view_data() void GLGizmosManager::update_data() { - if (!m_enabled) - return; - - const Selection& selection = m_parent.get_selection(); - - bool is_wipe_tower = selection.is_wipe_tower(); - enable_grabber(Move, 2, !is_wipe_tower); - enable_grabber(Rotate, 0, !is_wipe_tower); - enable_grabber(Rotate, 1, !is_wipe_tower); - - // BBS: when select multiple objects, uniform scale can be deselected, display the 0-5 grabbers - //bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); - //for (unsigned int i = 0; i < 6; ++i) - //{ - // enable_grabber(Scale, i, enable_scale_xyz); - //} - - if (m_common_gizmos_data) { + if (!m_enabled) return; + if (m_common_gizmos_data) m_common_gizmos_data->update(get_current() - ? get_current()->get_requirements() - : CommonGizmosDataID(0)); - } - - if (selection.is_single_full_instance()) - { - // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - set_scale(volume->get_instance_scaling_factor()); - set_rotation(Vec3d::Zero()); - // BBS - finish_cut_rotation(); - ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; - set_flattening_data(model_object); - set_sla_support_data(model_object); - set_painter_gizmo_data(); - } - else if (selection.is_single_volume() || selection.is_single_modifier()) - { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - set_scale(volume->get_volume_scaling_factor()); - set_rotation(Vec3d::Zero()); - // BBS - finish_cut_rotation(); - set_flattening_data(nullptr); - set_sla_support_data(nullptr); - set_painter_gizmo_data(); - } - else if (is_wipe_tower) - { - DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config; - set_scale(Vec3d::Ones()); - set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(proj_cfg.option("wipe_tower_rotation_angle"))->value)); - set_flattening_data(nullptr); - set_sla_support_data(nullptr); - set_painter_gizmo_data(); - } - else - { - set_scale(Vec3d::Ones()); - set_rotation(Vec3d::Zero()); - set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); - set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); - set_painter_gizmo_data(); - } + ? get_current()->get_requirements() + : CommonGizmosDataID(0)); + if (m_current != Undefined) m_gizmos[m_current]->data_changed(m_serializing); //BBS: GUI refactor: add object manipulation in gizmo m_object_manipulation.update_ui_from_settings(); @@ -517,124 +424,17 @@ bool GLGizmosManager::is_dragging() const return m_gizmos[m_current]->is_dragging(); } -void GLGizmosManager::start_dragging() -{ - if (! m_enabled || m_current == Undefined) - return; - m_gizmos[m_current]->start_dragging(); -} - -void GLGizmosManager::stop_dragging() -{ - if (! m_enabled || m_current == Undefined) - return; - - m_gizmos[m_current]->stop_dragging(); -} - -Vec3d GLGizmosManager::get_displacement() const -{ - if (!m_enabled) - return Vec3d::Zero(); - - return dynamic_cast(m_gizmos[Move].get())->get_displacement(); -} - -Vec3d GLGizmosManager::get_scale() const -{ - if (!m_enabled) - return Vec3d::Ones(); - - return dynamic_cast(m_gizmos[Scale].get())->get_scale(); -} - -void GLGizmosManager::set_scale(const Vec3d& scale) -{ - if (!m_enabled || m_gizmos.empty()) - return; - - dynamic_cast(m_gizmos[Scale].get())->set_scale(scale); -} - -Vec3d GLGizmosManager::get_scale_offset() const -{ - if (!m_enabled || m_gizmos.empty()) - return Vec3d::Zero(); - - return dynamic_cast(m_gizmos[Scale].get())->get_offset(); -} - -Vec3d GLGizmosManager::get_rotation() const -{ - if (!m_enabled || m_gizmos.empty()) - return Vec3d::Zero(); - - return dynamic_cast(m_gizmos[Rotate].get())->get_rotation(); -} - -void GLGizmosManager::set_rotation(const Vec3d& rotation) -{ - if (!m_enabled || m_gizmos.empty()) - return; - dynamic_cast(m_gizmos[Rotate].get())->set_rotation(rotation); -} - -// BBS -void GLGizmosManager::finish_cut_rotation() -{ - dynamic_cast(m_gizmos[Cut].get())->finish_rotation(); -} - -Vec3d GLGizmosManager::get_flattening_normal() const -{ - if (!m_enabled || m_gizmos.empty()) - return Vec3d::Zero(); - - return dynamic_cast(m_gizmos[Flatten].get())->get_flattening_normal(); -} - -void GLGizmosManager::set_flattening_data(const ModelObject* model_object) -{ - if (!m_enabled || m_gizmos.empty()) - return; - - dynamic_cast(m_gizmos[Flatten].get())->set_flattening_data(model_object); -} - -void GLGizmosManager::set_sla_support_data(ModelObject* model_object) -{ - if (! m_enabled - || m_gizmos.empty() - || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) - return; - - auto* gizmo_hollow = dynamic_cast(m_gizmos[Hollow].get()); - auto* gizmo_supports = dynamic_cast(m_gizmos[SlaSupports].get()); - gizmo_hollow->set_sla_support_data(model_object, m_parent.get_selection()); - gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection()); -} - -void GLGizmosManager::set_painter_gizmo_data() -{ - if (!m_enabled || m_gizmos.empty()) - return; - - dynamic_cast(m_gizmos[FdmSupports].get())->set_painter_gizmo_data(m_parent.get_selection()); - dynamic_cast(m_gizmos[Seam].get())->set_painter_gizmo_data(m_parent.get_selection()); - dynamic_cast(m_gizmos[MmuSegmentation].get())->set_painter_gizmo_data(m_parent.get_selection()); -} - // Returns true if the gizmo used the event to do something, false otherwise. bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (!m_enabled || m_gizmos.empty()) return false; - if (m_current == SlaSupports) + /*if (m_current == SlaSupports) return dynamic_cast(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Hollow) return dynamic_cast(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); - else if (m_current == FdmSupports) + else*/ if (m_current == FdmSupports) return dynamic_cast(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Seam) return dynamic_cast(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); @@ -642,8 +442,10 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Text) return dynamic_cast(m_gizmos[Text].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == Measure) + return dynamic_cast(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Cut) - return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == MeshBoolean) return dynamic_cast(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else @@ -676,8 +478,9 @@ ClippingPlane GLGizmosManager::get_assemble_view_clipping_plane() const bool GLGizmosManager::wants_reslice_supports_on_undo() const { - return (m_current == SlaSupports - && dynamic_cast(m_gizmos.at(SlaSupports).get())->has_backend_supports()); + return false; + // return (m_current == SlaSupports + // && dynamic_cast(m_gizmos.at(SlaSupports).get())->has_backend_supports()); } void GLGizmosManager::on_change_color_mode(bool is_dark) { @@ -692,7 +495,7 @@ void GLGizmosManager::render_current_gizmo() const m_gizmos[m_current]->render(); } -void GLGizmosManager::render_painter_gizmo() const +void GLGizmosManager::render_painter_gizmo() { // This function shall only be called when current gizmo is // derived from GLGizmoPainterBase. @@ -711,15 +514,6 @@ void GLGizmosManager::render_painter_assemble_view() const m_assemble_view_data->model_objects_clipper()->render_cut(); } -void GLGizmosManager::render_current_gizmo_for_picking_pass() const -{ - if (! m_enabled || m_current == Undefined) - - return; - - m_gizmos[m_current]->render_for_picking(); -} - void GLGizmosManager::render_overlay() { if (!m_enabled) @@ -740,11 +534,11 @@ std::string GLGizmosManager::get_tooltip() const return (curr != nullptr) ? curr->get_tooltip() : ""; } -bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) +bool GLGizmosManager::on_mouse_wheel(const wxMouseEvent &evt) { bool processed = false; - if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) { + if (/*m_current == SlaSupports || m_current == Hollow ||*/ m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) { float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown() // BBS @@ -760,253 +554,113 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) return processed; } -bool GLGizmosManager::on_mouse(wxMouseEvent& evt) -{ - // used to set a right up event as processed when needed - static bool pending_right_up = false; - - Point pos(evt.GetX(), evt.GetY()); - Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); - - Selection& selection = m_parent.get_selection(); - int selected_object_idx = selection.get_object_idx(); - bool processed = false; - - // when control is down we allow scene pan and rotation even when clicking over some object - bool control_down = evt.CmdDown(); - - // mouse anywhere - if (evt.Moving()) { - m_tooltip = update_hover_state(mouse_pos); - if (m_current == MmuSegmentation || m_current == FdmSupports || m_current == Text) - // BBS - gizmo_event(SLAGizmoEventType::Moving, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); - } else if (evt.LeftUp()) { - if (m_mouse_capture.left) { - processed = true; - m_mouse_capture.left = false; +bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) { + assert(m_enabled); + // keep information about events to process + struct MouseCapture + { + bool left = false; + bool middle = false; + bool right = false; + bool exist_tooltip = false; + MouseCapture() = default; + bool any() const { return left || middle || right; } + void reset() { + left = false; + middle = false; + right = false; } - else if (is_dragging()) { - switch (m_current) { - case Move: { m_parent.do_move(("Tool-Move")); break; } - case Scale: { m_parent.do_scale(("Tool-Scale")); break; } - case Rotate: { m_parent.do_rotate(("Tool-Rotate")); break; } - default: break; - } + }; + static MouseCapture mc; - stop_dragging(); - update_data(); + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - // updates camera target constraints - m_parent.refresh_camera_scene_box(); + EType gizmo = get_gizmo_from_mouse(mouse_pos); + bool selected_gizmo = gizmo != Undefined; - processed = true; - } -// else -// return false; - } - else if (evt.MiddleUp()) { - if (m_mouse_capture.middle) { - processed = true; - m_mouse_capture.middle = false; - } - else + // fast reaction on move mouse + if (mouse_event.Moving()) { + assert(!mc.any()); + if (selected_gizmo) { + mc.exist_tooltip = true; + update_hover_state(gizmo); + // at this moment is enebled to process mouse move under gizmo + // tools bar e.g. Do not interupt dragging. return false; + } else if (mc.exist_tooltip) { + // first move out of gizmo tool bar - unselect tooltip + mc.exist_tooltip = false; + update_hover_state(Undefined); + return false; + } + return false; } - else if (evt.RightUp()) { - if (pending_right_up) { - pending_right_up = false; + + if (selected_gizmo) { + // mouse is above toolbar + if (mouse_event.LeftDown() || mouse_event.LeftDClick()) { + mc.left = true; + open_gizmo(gizmo); return true; } - if (m_mouse_capture.right) { - processed = true; - m_mouse_capture.right = false; + else if (mouse_event.RightDown()) { + mc.right = true; + return true; } -// else -// return false; - } - else if (evt.Dragging() && !is_dragging()) { - if (m_mouse_capture.any()) - // if the button down was done on this toolbar, prevent from dragging into the scene - processed = true; -// else -// return false; - } - else if (evt.Dragging() && is_dragging()) { - if (!m_parent.get_wxglcanvas()->HasCapture()) - m_parent.get_wxglcanvas()->CaptureMouse(); - - m_parent.set_mouse_as_dragging(); - update(m_parent.mouse_ray(pos), pos); - - switch (m_current) - { - case Move: - { - // Apply new temporary offset - selection.translate(get_displacement()); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); - break; - } - case Scale: - { - // Apply new temporary scale factors - TransformationType transformation_type(TransformationType::Local_Absolute_Joint); - //if (evt.AltDown()) - // transformation_type.set_independent(); - selection.scale(get_scale(), transformation_type); - if (control_down && m_gizmos[m_current].get()->get_hover_id() < 6) - selection.translate(get_scale_offset(), true); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); - break; - } - case Rotate: - { - // Apply new temporary rotations - TransformationType transformation_type(TransformationType::World_Relative_Joint); - if (evt.AltDown()) - transformation_type.set_independent(); - selection.rotate(get_rotation(), transformation_type); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); - break; - } - default: - break; - } - - m_parent.set_as_dirty(); - processed = true; - } - - if (get_gizmo_idx_from_mouse(mouse_pos) == Undefined) { - // mouse is outside the toolbar - m_tooltip.clear(); - - if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || - m_current == Seam || m_current == MmuSegmentation || m_current == Text || m_current == Cut || m_current == MeshBoolean) - && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown())) - // the gizmo got the event and took some action, there is no need to do anything more - processed = true; - else if (!selection.is_empty() && grabber_contains_mouse()) { - update_data(); - selection.start_dragging(); - start_dragging(); - - // Let the plater know that the dragging started - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED)); - - if (m_current == Flatten) { - // Rotate the object so the normal points downward: - m_parent.do_flatten(get_flattening_normal(), L("Tool-Lay on Face")); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); - } - - m_parent.set_as_dirty(); - processed = true; - } - } - else if (evt.RightDown() && selected_object_idx != -1 && (m_current == SlaSupports || m_current == Hollow) - && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) { - // we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object - pending_right_up = true; - // event was taken care of by the SlaSupports gizmo - processed = true; - } - else if (evt.RightDown() && !control_down && selected_object_idx != -1 - && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) - && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) { - // event was taken care of by the FdmSupports / Seam / MMUPainting gizmo - processed = true; - } - else if (evt.Dragging() && m_parent.get_move_volume_id() != -1 - && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation)) - // don't allow dragging objects with the Sla gizmo on - processed = true; - else if (evt.Dragging() && !control_down - && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) - && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown())) { - // the gizmo got the event and took some action, no need to do anything more here - m_parent.set_as_dirty(); - processed = true; - } - else if (evt.Dragging() && control_down && (evt.LeftIsDown() || evt.RightIsDown())) { - // CTRL has been pressed while already dragging -> stop current action - if (evt.LeftIsDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), true); - else if (evt.RightIsDown()) - gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), true); - } - else if (evt.LeftUp() - && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) - && !m_parent.is_mouse_dragging() - && gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down)) { - // in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither - // object moving or selecting is suppressed in that case - processed = true; - } - else if (evt.LeftUp() && m_current == Flatten && m_gizmos[m_current]->get_hover_id() != -1) { - // to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active - selection.stop_dragging(); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); - processed = true; - } - else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) - && !m_parent.is_mouse_dragging() - && gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down)) { - processed = true; - } - else if (evt.LeftUp()) { - selection.stop_dragging(); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); - } - } - else { - // mouse inside toolbar - if (evt.LeftDown() || evt.LeftDClick()) { - m_mouse_capture.left = true; - m_mouse_capture.parent = &m_parent; - processed = true; - if (!selection.is_empty()) { - update_on_off_state(mouse_pos); - update_data(); - m_parent.set_as_dirty(); - try { - if ((int)m_hover >= 0 && (int)m_hover < m_gizmos.size()) { - std::string name = get_name_from_gizmo_etype(m_hover); - int count = m_gizmos[m_hover]->get_count(); - NetworkAgent* agent = GUI::wxGetApp().getAgent(); - if (agent) { - agent->track_update_property(name, std::to_string(count)); - } - } - } - catch (...) {} - } - } - else if (evt.MiddleDown()) { - m_mouse_capture.middle = true; - m_mouse_capture.parent = &m_parent; - } - else if (evt.RightDown()) { - m_mouse_capture.right = true; - m_mouse_capture.parent = &m_parent; + else if (mouse_event.MiddleDown()) { + mc.middle = true; return true; } } - return processed; + if (mc.any()) { + // Check if exist release of event started above toolbar? + if (mouse_event.Dragging()) { + if (!selected_gizmo && mc.exist_tooltip) { + // dragging out of gizmo let tooltip disapear + mc.exist_tooltip = false; + update_hover_state(Undefined); + } + // draging start on toolbar so no propagation into scene + return true; + } + else if (mc.left && mouse_event.LeftUp()) { + mc.left = false; + return true; + } + else if (mc.right && mouse_event.RightUp()) { + mc.right = false; + return true; + } + else if (mc.middle && mouse_event.MiddleUp()) { + mc.middle = false; + return true; + } + + // event out of window is not porocessed + // left down on gizmo -> keep down -> move out of window -> release left + if (mouse_event.Leaving()) mc.reset(); + } + return false; +} + +bool GLGizmosManager::on_mouse(const wxMouseEvent &mouse_event) +{ + if (!m_enabled) return false; + + // tool bar wants to use event? + if (gizmos_toolbar_on_mouse(mouse_event)) return true; + + // current gizmo wants to use event? + if (m_current != Undefined && + // check if gizmo override method could be slower than simple call virtual function + // &m_gizmos[m_current]->on_mouse != &GLGizmoBase::on_mouse && + m_gizmos[m_current]->on_mouse(mouse_event)) + return true; + + return false; } bool GLGizmosManager::on_char(wxKeyEvent& evt) @@ -1017,8 +671,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) bool processed = false; - if ((evt.GetModifiers() & ctrlMask) != 0) - { + if ((evt.GetModifiers() & ctrlMask) != 0) { switch (keyCode) { #ifdef __APPLE__ @@ -1036,23 +689,31 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) } } } - else if (!evt.HasModifiers()) - { + else if (!evt.HasModifiers()) { switch (keyCode) { // key ESC case WXK_ESCAPE: { - if (m_current != Undefined) - { - if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) + if (m_current != Undefined) { + if (m_current == Measure && gizmo_event(SLAGizmoEventType::Escape)) { + // do nothing + } else + //if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) reset_all_states(); processed = true; } break; } - //skip some keys when gizmo + case WXK_BACK: + case WXK_DELETE: + { + if ((m_current == Cut || m_current == Measure) && gizmo_event(SLAGizmoEventType::Delete)) + processed = true; + + break; + } case 'A': case 'a': { @@ -1130,8 +791,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) } } - if (!processed && !evt.HasModifiers()) - { + if (!processed && !evt.HasModifiers()) { if (handle_shortcut(keyCode)) processed = true; } @@ -1147,16 +807,8 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) const int keyCode = evt.GetKeyCode(); bool processed = false; - // todo: zhimin Each gizmo should handle key event in it own on_key() function - if (m_current == Cut) { - if (GLGizmoAdvancedCut *gizmo_cut = dynamic_cast(get_current())) { - return gizmo_cut->on_key(evt); - } - } - - if (evt.GetEventType() == wxEVT_KEY_UP) - { - if (m_current == SlaSupports || m_current == Hollow) + if (evt.GetEventType() == wxEVT_KEY_UP) { + /*if (m_current == SlaSupports || m_current == Hollow) { bool is_editing = true; bool is_rectangle_dragging = false; @@ -1189,6 +841,12 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) // capture number key processed = true; } + }*/ + if (m_current == Measure) { + if (keyCode == WXK_CONTROL) + gizmo_event(SLAGizmoEventType::CtrlUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + else if (keyCode == WXK_SHIFT) + gizmo_event(SLAGizmoEventType::ShiftUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); } // if (processed) @@ -1196,19 +854,16 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) } else if (evt.GetEventType() == wxEVT_KEY_DOWN) { - if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) + /*if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && dynamic_cast(get_current())->is_in_editing_mode()) { // m_parent.set_cursor(GLCanvas3D::Cross); processed = true; } - else if (m_current == Cut) - { - // BBS -#if 0 + else*/ if (m_current == Cut) { auto do_move = [this, &processed](double delta_z) { - GLGizmoAdvancedCut* cut = dynamic_cast(get_current()); - cut->set_cut_z(delta_z + cut->get_cut_z()); + GLGizmoCut3D* cut = dynamic_cast(get_current()); + cut->shift_cut(delta_z); processed = true; }; @@ -1216,10 +871,13 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) { case WXK_NUMPAD_UP: case WXK_UP: { do_move(1.0); break; } case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } + case WXK_SHIFT : case WXK_ALT: { + processed = get_current()->is_in_editing_mode(); + } default: { break; } } -#endif - } else if (m_current == Simplify && keyCode == WXK_ESCAPE) { + } + else if (m_current == Simplify && keyCode == WXK_ESCAPE) { GLGizmoSimplify *simplify = dynamic_cast(get_current()); if (simplify != nullptr) processed = simplify->on_esc_key_down(); @@ -1260,6 +918,12 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) wxGetApp().imgui()->set_requires_extra_frame(); } } + else if (m_current == Measure) { + if (keyCode == WXK_CONTROL) + gizmo_event(SLAGizmoEventType::CtrlDown, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + else if (keyCode == WXK_SHIFT) + gizmo_event(SLAGizmoEventType::ShiftDown, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + } } if (processed) @@ -1272,47 +936,43 @@ void GLGizmosManager::update_after_undo_redo(const UndoRedo::Snapshot& snapshot) { update_data(); m_serializing = false; - if (m_current == SlaSupports - && snapshot.snapshot_data.flags & UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS) - dynamic_cast(m_gizmos[SlaSupports].get())->reslice_SLA_supports(true); + // if (m_current == SlaSupports + // && snapshot.snapshot_data.flags & UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS) + // dynamic_cast(m_gizmos[SlaSupports].get())->reslice_SLA_supports(true); } -void GLGizmosManager::render_background(float left, float top, float right, float bottom, float border) const +void GLGizmosManager::render_background(float left, float top, float right, float bottom, float border_w, float border_h) const { - unsigned int tex_id = m_background_texture.texture.get_id(); - float tex_width = (float)m_background_texture.texture.get_width(); - float tex_height = (float)m_background_texture.texture.get_height(); - if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) - { + const unsigned int tex_id = m_background_texture.texture.get_id(); + const float tex_width = float(m_background_texture.texture.get_width()); + const float tex_height = float(m_background_texture.texture.get_height()); + if (tex_id != 0 && tex_width > 0 && tex_height > 0) { //BBS: GUI refactor: remove the corners of gizmo - float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; - float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + const float inv_tex_width = 1.0f / tex_width; + const float inv_tex_height = 1.0f / tex_height; - float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; - float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; - float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; - float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + const float internal_left_uv = float(m_background_texture.metadata.left) * inv_tex_width; + const float internal_right_uv = 1.0f - float(m_background_texture.metadata.right) * inv_tex_width; + const float internal_top_uv = 1.0f - float(m_background_texture.metadata.top) * inv_tex_height; + const float internal_bottom_uv = float(m_background_texture.metadata.bottom) * inv_tex_height; GLTexture::render_sub_texture(tex_id, left, right, bottom, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); /* - float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; - float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; - - float internal_left = left + border; - float internal_right = right - border; - float internal_top = top - border; - float internal_bottom = bottom + border; + const float internal_left = left + border_w; + const float internal_right = right - border_w; + const float internal_top = top - border_h; + const float internal_bottom = bottom + border_h; // float left_uv = 0.0f; - float right_uv = 1.0f; - float top_uv = 1.0f; - float bottom_uv = 0.0f; + const float right_uv = 1.0f; + const float top_uv = 1.0f; + const float bottom_uv = 0.0f; - float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; - float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; - float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; - float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + const float internal_left_uv = float(m_background_texture.metadata.left) * inv_tex_width; + const float internal_right_uv = 1.0f - float(m_background_texture.metadata.right) * inv_tex_width; + const float internal_top_uv = 1.0f - float(m_background_texture.metadata.top) * inv_tex_height; + const float internal_bottom_uv = float(m_background_texture.metadata.bottom) * inv_tex_height; // top-left corner GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); @@ -1346,7 +1006,7 @@ void GLGizmosManager::render_background(float left, float top, float right, floa void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_type) const { - std::vector selectable_idxs = get_selectable_idxs(); + const std::vector selectable_idxs = get_selectable_idxs(); if (selectable_idxs.empty()) return; float cnv_w = (float)m_parent.get_canvas_size().get_width(); @@ -1365,18 +1025,18 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t if (idx == highlighted_type) { int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); - unsigned int tex_id = m_arrow_texture.texture.get_id(); + unsigned int tex_id = m_arrow_texture.get_id(); float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; - float internal_left_uv = (float)m_arrow_texture.metadata.left * inv_tex_width; - float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_width; - float internal_top_uv = 1.0f - (float)m_arrow_texture.metadata.top * inv_tex_height; - float internal_bottom_uv = (float)m_arrow_texture.metadata.bottom * inv_tex_height; + const float left_uv = 0.0f; + const float right_uv = 1.0f; + const float top_uv = 1.0f; + const float bottom_uv = 0.0f; - float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width(); + float arrow_sides_ratio = (float)m_arrow_texture.get_height() / (float)m_arrow_texture.get_width(); - GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * 2.2f * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size * 1.6f , zoomed_top_y + zoomed_icons_size * 0.6f, { { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv } }); + GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * 2.2f * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size * 1.6f , zoomed_top_y + zoomed_icons_size * 0.6f, { { left_uv, bottom_uv }, { left_uv, top_uv }, { right_uv, top_uv }, { right_uv, bottom_uv } }); break; } zoomed_top_y -= zoomed_stride_y; @@ -1387,119 +1047,98 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t //when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-top void GLGizmosManager::do_render_overlay() const { - std::vector selectable_idxs = get_selectable_idxs(); + const std::vector selectable_idxs = get_selectable_idxs(); if (selectable_idxs.empty()) return; - float cnv_w = (float)m_parent.get_canvas_size().get_width(); - float cnv_h = (float)m_parent.get_canvas_size().get_height(); - float zoom = (float)wxGetApp().plater()->get_camera().get_zoom(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); + const Size cnv_size = m_parent.get_canvas_size(); + const float cnv_w = (float)cnv_size.get_width(); + const float cnv_h = (float)cnv_size.get_height(); - float height = get_scaled_total_height(); - float width = get_scaled_total_width(); - float zoomed_border = m_layout.scaled_border() * inv_zoom; + if (cnv_w == 0 || cnv_h == 0) + return; - float zoomed_top_x; + const float inv_cnv_w = 1.0f / cnv_w; + const float inv_cnv_h = 1.0f / cnv_h; + + const float height = 2.0f * get_scaled_total_height() * inv_cnv_h; + const float width = 2.0f * get_scaled_total_width() * inv_cnv_w; + const float border_h = 2.0f * m_layout.scaled_border() * inv_cnv_h; + const float border_w = 2.0f * m_layout.scaled_border() * inv_cnv_w; + + float top_x; if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) { - zoomed_top_x = 0.5f * m_parent.get_assembly_paint_toolbar_width() * inv_zoom; + top_x = m_parent.get_assembly_paint_toolbar_width() * inv_cnv_w; } else { //BBS: GUI refactor: GLToolbar&&Gizmo adjust -#if BBS_TOOLBAR_ON_TOP float main_toolbar_width = (float)m_parent.get_main_toolbar_width(); float assemble_view_width = (float)m_parent.get_assemble_view_toolbar_width(); float collapse_width = (float)m_parent.get_collapse_toolbar_width(); + float separator_width = (float)m_parent.get_separator_toolbar_width(); //float space_width = GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale(); //float zoomed_top_x = 0.5f *(cnv_w + main_toolbar_width - 2 * space_width - width) * inv_zoom; - float main_toolbar_left = std::max(-0.5f * cnv_w, -0.5f * (main_toolbar_width + get_scaled_total_width() + assemble_view_width - collapse_width)) * inv_zoom; + float main_toolbar_left = std::max(-0.5f * cnv_w, -0.5f * (main_toolbar_width + get_scaled_total_width() + assemble_view_width + separator_width - collapse_width)); //float zoomed_top_x = 0.5f *(main_toolbar_width + collapse_width - width - assemble_view_width) * inv_zoom; - zoomed_top_x = main_toolbar_left + (main_toolbar_width)*inv_zoom; + top_x = main_toolbar_left + main_toolbar_width + separator_width / 2; + top_x = top_x * inv_cnv_w * 2; } - float zoomed_top_y = 0.5f * cnv_h * inv_zoom; -#else - //float zoomed_top_x = (-0.5f * cnv_w) * inv_zoom; - //float zoomed_top_y = (0.5f * height) * inv_zoom; - float zoomed_top_x = (0.5f * cnv_w - width) * inv_zoom; - float main_toolbar_height = (float)m_parent.get_main_toolbar_height(); - float assemble_view_height = (float)m_parent.get_assemble_view_toolbar_height(); - //float space_height = GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale(); - float zoomed_top_y = 0.5f * (height + assemble_view_height - main_toolbar_height) * inv_zoom; -#endif - //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": zoomed_top_y %1%, space_height %2%, main_toolbar_height %3% zoomed_top_x %4%") % zoomed_top_y % space_height % main_toolbar_height % zoomed_top_x; + float top_y = 1.0f; - float zoomed_left = zoomed_top_x; - float zoomed_top = zoomed_top_y; - float zoomed_right = zoomed_left + width * inv_zoom; - float zoomed_bottom = zoomed_top - height * inv_zoom; + render_background(top_x, top_y, top_x + width, top_y - height, border_w, border_h); - render_background(zoomed_left, zoomed_top, zoomed_right, zoomed_bottom, zoomed_border); + top_x += border_w; + top_y -= border_h; - zoomed_top_x += zoomed_border; - zoomed_top_y -= zoomed_border; - - float icons_size = m_layout.scaled_icons_size(); - float zoomed_icons_size = icons_size * inv_zoom; - float zoomed_stride_y = m_layout.scaled_stride_y() * inv_zoom; + const float icons_size_x = 2.0f * m_layout.scaled_icons_size() * inv_cnv_w; + const float icons_size_y = 2.0f * m_layout.scaled_icons_size() * inv_cnv_h; //BBS: GUI refactor: GLToolbar&&Gizmo adjust - float zoomed_stride_x = m_layout.scaled_stride_x() * inv_zoom; + const float stride_x = 2.0f * m_layout.scaled_stride_x() * inv_cnv_w; - unsigned int icons_texture_id = m_icons_texture.get_id(); - int tex_width = m_icons_texture.get_width(); - int tex_height = m_icons_texture.get_height(); + const unsigned int icons_texture_id = m_icons_texture.get_id(); + const int tex_width = m_icons_texture.get_width(); + const int tex_height = m_icons_texture.get_height(); - if ((icons_texture_id == 0) || (tex_width <= 1) || (tex_height <= 1)) + if (icons_texture_id == 0 || tex_width <= 1 || tex_height <= 1) return; - float du = (float)(tex_width - 1) / (6.0f * (float)tex_width); // 6 is the number of possible states if the icons - float dv = (float)(tex_height - 1) / (float)(m_gizmos.size() * tex_height); + const float du = (float)(tex_width - 1) / (6.0f * (float)tex_width); // 6 is the number of possible states if the icons + const float dv = (float)(tex_height - 1) / (float)(m_gizmos.size() * tex_height); // tiles in the texture are spaced by 1 pixel - float u_offset = 1.0f / (float)tex_width; - float v_offset = 1.0f / (float)tex_height; + const float u_offset = 1.0f / (float)tex_width; + const float v_offset = 1.0f / (float)tex_height; bool is_render_current = false; - for (size_t idx : selectable_idxs) - { + for (size_t idx : selectable_idxs) { GLGizmoBase* gizmo = m_gizmos[idx].get(); - unsigned int sprite_id = gizmo->get_sprite_id(); + const unsigned int sprite_id = gizmo->get_sprite_id(); // higlighted state needs to be decided first so its highlighting in every other state - int icon_idx = (m_highlight.first == idx ? (m_highlight.second ? 4 : 5) : (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable()? 0 : 3))); + const int icon_idx = (m_highlight.first == idx ? (m_highlight.second ? 4 : 5) : (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable()? 0 : 3))); - float v_top = v_offset + sprite_id * dv; - float u_left = u_offset + icon_idx * du; - float v_bottom = v_top + dv - v_offset; - float u_right = u_left + du - u_offset; - - GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); + const float u_left = u_offset + icon_idx * du; + const float u_right = u_left + du - u_offset; + const float v_top = v_offset + sprite_id * dv; + const float v_bottom = v_top + dv - v_offset; + GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + icons_size_x, top_y - icons_size_y, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (idx == m_current) { //BBS: GUI refactor: GLToolbar&&Gizmo adjust //render_input_window uses a different coordination(imgui) //1. no need to scale by camera zoom, set {0,0} at left-up corner for imgui -#if BBS_TOOLBAR_ON_TOP //gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top); - gizmo->render_input_window(0.5 * cnv_w + zoomed_top_x * zoom, height, cnv_h); + gizmo->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h); is_render_current = true; -#else - float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height(); - //gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top); - gizmo->render_input_window(cnv_w - width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top); -#endif } -#if BBS_TOOLBAR_ON_TOP - zoomed_top_x += zoomed_stride_x; -#else - zoomed_top_y -= zoomed_stride_y; -#endif + top_x += stride_x; } // BBS simplify gizmo is not a selected gizmo and need to render input window if (!is_render_current && m_current != Undefined) { - m_gizmos[m_current]->render_input_window(0.5 * cnv_w + zoomed_top_x * zoom, height, cnv_h); + m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h); } } @@ -1546,15 +1185,16 @@ GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& g return GLGizmosManager::EType::Undefined; } -bool GLGizmosManager::generate_icons_texture() const +bool GLGizmosManager::generate_icons_texture() { std::string path = resources_dir() + "/images/"; std::vector filenames; for (size_t idx=0; idxget_icon_filename(); + const std::string& icon_filename = gizmo->get_icon_filename(); if (!icon_filename.empty()) filenames.push_back(path + icon_filename); } @@ -1580,76 +1220,68 @@ bool GLGizmosManager::generate_icons_texture() const return res; } -void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) +void GLGizmosManager::update_hover_state(const EType &type) { - if (!m_enabled) + assert(m_enabled); + if (type == Undefined) { + m_hover = Undefined; + m_tooltip.clear(); return; - - size_t idx = get_gizmo_idx_from_mouse(mouse_pos); - if (idx != Undefined && m_gizmos[idx]->is_activable() && m_hover == idx) { - activate_gizmo(m_current == idx ? Undefined : (EType)idx); - // BBS - wxGetApp().obj_list()->select_object_item((EType) idx <= Scale || (EType) idx == Text); - } -} - -std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) -{ - std::string name = ""; - - if (!m_enabled) - return name; - - m_hover = Undefined; - - size_t idx = get_gizmo_idx_from_mouse(mouse_pos); - if (idx != Undefined) { - name = m_gizmos[idx]->get_name(); - - if (m_gizmos[idx]->is_activable()) - m_hover = (EType)idx; } - return name; + const GLGizmoBase &hovered_gizmo = *m_gizmos[type]; + m_hover = hovered_gizmo.is_activable() ? type : Undefined; + m_tooltip = hovered_gizmo.get_name(); } bool GLGizmosManager::activate_gizmo(EType type) { - if (m_gizmos.empty() || m_current == type) - return true; + assert(!m_gizmos.empty()); - GLGizmoBase* old_gizmo = m_current == Undefined ? nullptr : m_gizmos[m_current].get(); - GLGizmoBase* new_gizmo = type == Undefined ? nullptr : m_gizmos[type].get(); + // already activated + if (m_current == type) return true; - if (old_gizmo) { - //if (m_current == Text) { - // wxGetApp().imgui()->destroy_fonts_texture(); - //} - old_gizmo->set_state(GLGizmoBase::Off); - if (old_gizmo->get_state() != GLGizmoBase::Off) + if (m_current != Undefined) { + // clean up previous gizmo + GLGizmoBase &old_gizmo = *m_gizmos[m_current]; + old_gizmo.set_state(GLGizmoBase::Off); + if (old_gizmo.get_state() != GLGizmoBase::Off) return false; // gizmo refused to be turned off, do nothing. - if (! m_parent.get_gizmos_manager().is_serializing() - && old_gizmo->wants_enter_leave_snapshots()) - Plater::TakeSnapshot snapshot(wxGetApp().plater(), - old_gizmo->get_gizmo_leaving_text(), - UndoRedo::SnapshotType::LeavingGizmoWithAction); + old_gizmo.unregister_raycasters_for_picking(); + + if (!m_serializing && old_gizmo.wants_enter_leave_snapshots()) + Plater::TakeSnapshot + snapshot(wxGetApp().plater(), + old_gizmo.get_gizmo_leaving_text(), + UndoRedo::SnapshotType::LeavingGizmoWithAction); } - if (new_gizmo && ! m_parent.get_gizmos_manager().is_serializing() - && new_gizmo->wants_enter_leave_snapshots()) + if (type == Undefined) { + // it is deactivation of gizmo + m_current = Undefined; + return true; + } + + // set up new gizmo + GLGizmoBase& new_gizmo = *m_gizmos[type]; + if (!new_gizmo.is_activable()) return false; + + if (!m_serializing && new_gizmo.wants_enter_leave_snapshots()) Plater::TakeSnapshot snapshot(wxGetApp().plater(), - new_gizmo->get_gizmo_entering_text(), - UndoRedo::SnapshotType::EnteringGizmo); + new_gizmo.get_gizmo_entering_text(), + UndoRedo::SnapshotType::EnteringGizmo); m_current = type; - - if (new_gizmo) { - //if (m_current == Text) { - // wxGetApp().imgui()->load_fonts_texture(); - //} - new_gizmo->set_state(GLGizmoBase::On); + new_gizmo.set_state(GLGizmoBase::On); + if (new_gizmo.get_state() != GLGizmoBase::On) { + m_current = Undefined; + return false; // gizmo refused to be turned on. } + + new_gizmo.register_raycasters_for_picking(); + + // sucessful activation of gizmo return true; } @@ -1666,11 +1298,11 @@ bool GLGizmosManager::grabber_contains_mouse() const bool GLGizmosManager::is_in_editing_mode(bool error_notification) const { - if (m_current != SlaSupports || ! dynamic_cast(get_current())->is_in_editing_mode()) + //if (m_current != SlaSupports || ! dynamic_cast(get_current())->is_in_editing_mode()) return false; // BBS: remove SLA editing notification - return true; + //return true; } @@ -1681,12 +1313,6 @@ bool GLGizmosManager::is_hiding_instances() const && m_common_gizmos_data->instances_hider()->is_valid()); } - -int GLGizmosManager::get_shortcut_key(GLGizmosManager::EType type) const -{ - return m_gizmos[type]->get_shortcut_key(); -} - std::string get_name_from_gizmo_etype(GLGizmosManager::EType type) { switch (type) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index fefb85b6b9..02169e08d4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2022 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Filip Sykala @Jony01, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GUI_GLGizmosManager_hpp_ #define slic3r_GUI_GLGizmosManager_hpp_ @@ -31,16 +35,24 @@ class CommonGizmosDataPool; class GizmoObjectManipulation; class Rect { - float m_left; - float m_top; - float m_right; - float m_bottom; + float m_left{ 0.0f }; + float m_top{ 0.0f }; + float m_right{ 0.0f }; + float m_bottom{ 0.0f }; public: - Rect() : m_left(0.0f) , m_top(0.0f) , m_right(0.0f) , m_bottom(0.0f) {} - + Rect() = default; Rect(float left, float top, float right, float bottom) : m_left(left) , m_top(top) , m_right(right) , m_bottom(bottom) {} + bool operator == (const Rect& other) const { + if (std::abs(m_left - other.m_left) > EPSILON) return false; + if (std::abs(m_top - other.m_top) > EPSILON) return false; + if (std::abs(m_right - other.m_right) > EPSILON) return false; + if (std::abs(m_bottom - other.m_bottom) > EPSILON) return false; + return true; + } + bool operator != (const Rect& other) const { return !operator==(other); } + float get_left() const { return m_left; } void set_left(float left) { m_left = left; } @@ -76,11 +88,12 @@ public: // BBS Text, MmuSegmentation, + Measure, Simplify, - SlaSupports, + //SlaSupports, // BBS //FaceRecognition, - Hollow, + //Hollow, Undefined, }; @@ -108,10 +121,10 @@ private: GLCanvas3D& m_parent; bool m_enabled; std::vector> m_gizmos; - mutable GLTexture m_icons_texture; - mutable bool m_icons_texture_dirty; + GLTexture m_icons_texture; + bool m_icons_texture_dirty; BackgroundTexture m_background_texture; - BackgroundTexture m_arrow_texture; + GLTexture m_arrow_texture; Layout m_layout; EType m_current; EType m_hover; @@ -121,24 +134,10 @@ private: GizmoObjectManipulation m_object_manipulation; std::vector get_selectable_idxs() const; - size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const; + EType get_gizmo_from_mouse(const Vec2d &mouse_pos) const; bool activate_gizmo(EType type); - struct MouseCapture - { - bool left; - bool middle; - bool right; - GLCanvas3D* parent; - - MouseCapture() { reset(); } - - bool any() const { return left || middle || right; } - void reset() { left = middle = right = false; parent = nullptr; } - }; - - MouseCapture m_mouse_capture; std::string m_tooltip; bool m_serializing; std::unique_ptr m_common_gizmos_data; @@ -147,6 +146,15 @@ private: std::map icon_list; bool m_is_dark = false; + + /// + /// Process mouse event on gizmo toolbar + /// + /// Event descriptor + /// TRUE when take responsibility for event otherwise FALSE. + /// On true, event should not be process by others. + /// On false, event should be process by others. + bool gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event); public: std::unique_ptr m_assemble_view_data; @@ -172,7 +180,7 @@ public: float get_layout_scale(); - bool init_arrow(const BackgroundTexture::Metadata& arrow_texture); + bool init_arrow(const std::string& filename); template void load(Archive& ar) @@ -218,14 +226,15 @@ public: void refresh_on_off_state(); void reset_all_states(); - bool is_serializing() const { return m_serializing; } bool open_gizmo(EType type); bool check_gizmos_closed_except(EType) const; void set_hover_id(int id); - void enable_grabber(EType type, unsigned int id, bool enable); - void update(const Linef3& mouse_ray, const Point& mouse_pos); + /// + /// Distribute information about different data into active gizmo + /// Should be called when selection changed + /// void update_data(); void update_assemble_view_data(); @@ -238,21 +247,6 @@ public: bool handle_shortcut(int key); bool is_dragging() const; - void start_dragging(); - void stop_dragging(); - - Vec3d get_displacement() const; - - Vec3d get_scale() const; - void set_scale(const Vec3d& scale); - - Vec3d get_scale_offset() const; - - Vec3d get_rotation() const; - void set_rotation(const Vec3d& rotation); - - // BBS - void finish_cut_rotation(); //BBS void* get_icon_texture_id(MENU_ICON_NAME icon) { @@ -268,15 +262,6 @@ public: return nullptr; } - Vec3d get_flattening_normal() const; - - void set_flattening_data(const ModelObject* model_object); - - void set_sla_support_data(ModelObject* model_object); - - void set_painter_gizmo_data(); - - bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); ClippingPlane get_clipping_plane() const; ClippingPlane get_assemble_view_clipping_plane() const; bool wants_reslice_supports_on_undo() const; @@ -286,8 +271,7 @@ public: void on_change_color_mode(bool is_dark); void render_current_gizmo() const; - void render_current_gizmo_for_picking_pass() const; - void render_painter_gizmo() const; + void render_painter_gizmo(); void render_painter_assemble_view() const; void render_overlay(); @@ -296,15 +280,14 @@ public: std::string get_tooltip() const; - bool on_mouse(wxMouseEvent& evt); - bool on_mouse_wheel(wxMouseEvent& evt); + bool on_mouse(const wxMouseEvent &mouse_event); + bool on_mouse_wheel(const wxMouseEvent &evt); bool on_char(wxKeyEvent& evt); bool on_key(wxKeyEvent& evt); void update_after_undo_redo(const UndoRedo::Snapshot& snapshot); int get_selectable_icons_cnt() const { return get_selectable_idxs().size(); } - int get_shortcut_key(GLGizmosManager::EType) const; // To end highlight set gizmo = undefined void set_highlight(EType gizmo, bool highlight_shown) { m_highlight = std::pair(gizmo, highlight_shown); } @@ -317,14 +300,19 @@ public: bool get_uniform_scaling() const { return m_object_manipulation.get_uniform_scaling();} private: - void render_background(float left, float top, float right, float bottom, float border) const; + bool gizmo_event(SLAGizmoEventType action, + const Vec2d & mouse_position = Vec2d::Zero(), + bool shift_down = false, + bool alt_down = false, + bool control_down = false); + + void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const; void do_render_overlay() const; - bool generate_icons_texture() const; + bool generate_icons_texture(); - void update_on_off_state(const Vec2d& mouse_pos); - std::string update_hover_state(const Vec2d& mouse_pos); + void update_hover_state(const EType &type); bool grabber_contains_mouse() const; }; diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index 2cdd8aac36..1a5b891598 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -86,7 +86,7 @@ void GizmoObjectManipulation::update_settings_value(const Selection& selection) ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); m_new_position = volume->get_instance_offset(); if (m_world_coordinates) { @@ -118,7 +118,7 @@ void GizmoObjectManipulation::update_settings_value(const Selection& selection) } else if (selection.is_single_modifier() || selection.is_single_volume()) { // the selection contains a single volume - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; @@ -217,7 +217,7 @@ void GizmoObjectManipulation::update_reset_buttons_visibility() const Selection& selection = m_glcanvas.get_selection(); if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Vec3d rotation; Vec3d scale; double min_z = 0.; @@ -259,7 +259,7 @@ void GizmoObjectManipulation::change_position_value(int axis, double value) position(axis) = value; Selection& selection = m_glcanvas.get_selection(); - selection.start_dragging(); + selection.setup_cache(); selection.translate(position - m_cache.position, selection.requires_local_axes()); m_glcanvas.do_move(L("Set Position")); @@ -287,7 +287,7 @@ void GizmoObjectManipulation::change_rotation_value(int axis, double value) transformation_type.set_local(); } - selection.start_dragging(); + selection.setup_cache(); selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); @@ -331,14 +331,14 @@ void GizmoObjectManipulation::change_size_value(int axis, double value) Vec3d ref_size = m_cache.size; if (selection.is_single_volume() || selection.is_single_modifier()) { - Vec3d instance_scale = wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->instances[0]->get_transformation().get_scaling_factor(); - ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); + Vec3d instance_scale = wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->instances[0]->get_transformation().get_scaling_factor(); + ref_size = selection.get_first_volume()->bounding_box().size(); ref_size = Vec3d(instance_scale[0] * ref_size[0], instance_scale[1] * ref_size[1], instance_scale[2] * ref_size[2]); } else if (selection.is_single_full_instance()) ref_size = m_world_coordinates ? selection.get_unscaled_instance_bounding_box().size() : - wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); + wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size(); this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); @@ -363,7 +363,7 @@ void GizmoObjectManipulation::do_scale(int axis, const Vec3d &scale) const if (m_uniform_scale/* || selection.requires_uniform_scale()*/) scaling_factor = scale(axis) * Vec3d::Ones(); - selection.start_dragging(); + selection.setup_cache(); selection.scale(scaling_factor * 0.01, transformation_type); m_glcanvas.do_scale(L("Set Scale")); } @@ -391,7 +391,7 @@ void GizmoObjectManipulation::reset_position_value() Selection& selection = m_glcanvas.get_selection(); if (selection.is_single_volume() || selection.is_single_modifier()) { - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + GLVolume* volume = const_cast(selection.get_first_volume()); volume->set_volume_offset(Vec3d::Zero()); } else if (selection.is_single_full_instance()) { @@ -414,7 +414,7 @@ void GizmoObjectManipulation::reset_rotation_value() Selection& selection = m_glcanvas.get_selection(); if (selection.is_single_volume() || selection.is_single_modifier()) { - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + GLVolume* volume = const_cast(selection.get_first_volume()); volume->set_volume_rotation(Vec3d::Zero()); } else if (selection.is_single_full_instance()) { @@ -450,7 +450,7 @@ void GizmoObjectManipulation::set_uniform_scaling(const bool new_value) if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); // Is the angle close to a multiple of 90 degrees? if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { diff --git a/src/slic3r/GUI/IMSlider.cpp b/src/slic3r/GUI/IMSlider.cpp index 0c8094a4bb..3f4dbd00c0 100644 --- a/src/slic3r/GUI/IMSlider.cpp +++ b/src/slic3r/GUI/IMSlider.cpp @@ -586,8 +586,8 @@ void IMSlider::draw_colored_band(const ImRect& groove, const ImRect& slideable_r }; //draw main colored band const int default_color_idx = m_mode == MultiAsSingle ? std::max(m_only_extruder - 1, 0) : 0; - std::arrayrgba = decode_color_to_float_array(m_extruder_colors[default_color_idx]); - ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + ColorRGBA rgba = decode_color_to_float_array(m_extruder_colors[default_color_idx]); + ImU32 band_clr = ImGuiWrapper::to_ImU32(rgba); draw_main_band(band_clr); static float tick_pos; @@ -605,8 +605,8 @@ void IMSlider::draw_colored_band(const ImRect& groove, const ImRect& slideable_r const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : get_color_for_tool_change_tick(tick_it); if (!clr_str.empty()) { - std::arrayrgba = decode_color_to_float_array(clr_str); - ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + ColorRGBA rgba = decode_color_to_float_array(clr_str); + ImU32 band_clr = ImGuiWrapper::to_ImU32(rgba); if (tick_it->tick == 0) draw_main_band(band_clr); else @@ -1323,9 +1323,9 @@ void IMSlider::render_add_menu() } else if (begin_menu(_u8L("Change Filament").c_str())) { for (int i = 0; i < extruder_num; i++) { - std::array rgba = decode_color_to_float_array(m_extruder_colors[i]); - ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); - if (rgba[3] == 0) + ColorRGBA rgba = decode_color_to_float_array(m_extruder_colors[i]); + ImU32 icon_clr = ImGuiWrapper::to_ImU32(rgba); + if (rgba.a() == 0) icon_clr = 0; if (menu_item_with_icon((_u8L("Filament ") + std::to_string(i + 1)).c_str(), "", ImVec2(14, 14) * m_scale, icon_clr, false, true, &hovered)) add_code_as_tick(ToolChange, i + 1); if (hovered) { show_tooltip(_u8L("Change filament at the beginning of this layer.")); } @@ -1373,8 +1373,8 @@ void IMSlider::render_edit_menu(const TickCode& tick) } else if (begin_menu(_u8L("Change Filament").c_str())) { for (int i = 0; i < extruder_num; i++) { - std::array rgba = decode_color_to_float_array(m_extruder_colors[i]); - ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + ColorRGBA rgba = decode_color_to_float_array(m_extruder_colors[i]); + ImU32 icon_clr = ImGuiWrapper::to_ImU32(rgba); if (menu_item_with_icon((_u8L("Filament ") + std::to_string(i + 1)).c_str(), "", ImVec2(14, 14) * m_scale, icon_clr)) add_code_as_tick(ToolChange, i + 1); } end_menu(); diff --git a/src/slic3r/GUI/IMSlider_Utils.hpp b/src/slic3r/GUI/IMSlider_Utils.hpp deleted file mode 100644 index bfe5545a3b..0000000000 --- a/src/slic3r/GUI/IMSlider_Utils.hpp +++ /dev/null @@ -1,198 +0,0 @@ -#ifndef slic3r_IMSlider_Utils_hpp_ -#define slic3r_IMSlider_Utils_hpp_ - -#include -#include - -#include "wx/colour.h" - -//double epsilon() { return 0.0011; } - -class ColorGenerator -{ - // Some of next code is borrowed from https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both - typedef struct { - double r; // a fraction between 0 and 1 - double g; // a fraction between 0 and 1 - double b; // a fraction between 0 and 1 - } rgb; - - typedef struct { - double h; // angle in degrees - double s; // a fraction between 0 and 1 - double v; // a fraction between 0 and 1 - } hsv; - - //static hsv rgb2hsv(rgb in); - //static rgb hsv2rgb(hsv in); - - hsv rgb2hsv(rgb in) - { - hsv out; - double min, max, delta; - - min = in.r < in.g ? in.r : in.g; - min = min < in.b ? min : in.b; - - max = in.r > in.g ? in.r : in.g; - max = max > in.b ? max : in.b; - - out.v = max; // v - delta = max - min; - if (delta < 0.00001) - { - out.s = 0; - out.h = 0; // undefined, maybe nan? - return out; - } - if (max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash - out.s = (delta / max); // s - } - else { - // if max is 0, then r = g = b = 0 - // s = 0, h is undefined - out.s = 0.0; - out.h = NAN; // its now undefined - return out; - } - if (in.r >= max) // > is bogus, just keeps compilor happy - out.h = (in.g - in.b) / delta; // between yellow & magenta - else - if (in.g >= max) - out.h = 2.0 + (in.b - in.r) / delta; // between cyan & yellow - else - out.h = 4.0 + (in.r - in.g) / delta; // between magenta & cyan - - out.h *= 60.0; // degrees - - if (out.h < 0.0) - out.h += 360.0; - - return out; - } - - hsv rgb2hsv(const std::string& str_clr_in) - { - wxColour clr(str_clr_in); - rgb in = { clr.Red() / 255.0, clr.Green() / 255.0, clr.Blue() / 255.0 }; - return rgb2hsv(in); - } - - - rgb hsv2rgb(hsv in) - { - double hh, p, q, t, ff; - long i; - rgb out; - - if (in.s <= 0.0) { // < is bogus, just shuts up warnings - out.r = in.v; - out.g = in.v; - out.b = in.v; - return out; - } - hh = in.h; - if (hh >= 360.0) hh -= 360.0;//hh = 0.0; - hh /= 60.0; - i = (long)hh; - ff = hh - i; - p = in.v * (1.0 - in.s); - q = in.v * (1.0 - (in.s * ff)); - t = in.v * (1.0 - (in.s * (1.0 - ff))); - - switch (i) { - case 0: - out.r = in.v; - out.g = t; - out.b = p; - break; - case 1: - out.r = q; - out.g = in.v; - out.b = p; - break; - case 2: - out.r = p; - out.g = in.v; - out.b = t; - break; - - case 3: - out.r = p; - out.g = q; - out.b = in.v; - break; - case 4: - out.r = t; - out.g = p; - out.b = in.v; - break; - case 5: - default: - out.r = in.v; - out.g = p; - out.b = q; - break; - } - return out; - } - - std::random_device rd; - -public: - - ColorGenerator() {} - ~ColorGenerator() {} - - double rand_val() - { - std::mt19937 rand_generator(rd()); - - // this value will be used for Saturation and Value - // to avoid extremely light/dark colors, take this value from range [0.65; 1.0] - std::uniform_real_distribution distrib(0.65, 1.0); - return distrib(rand_generator); - } - - - std::string get_opposite_color(const std::string& color) - { - std::string opp_color = ""; - - hsv hsv_clr = rgb2hsv(color); - hsv_clr.h += 65; // 65 instead 60 to avoid circle values - hsv_clr.s = rand_val(); - hsv_clr.v = rand_val(); - - rgb rgb_opp_color = hsv2rgb(hsv_clr); - - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), (unsigned char)(rgb_opp_color.r * 255), (unsigned char)(rgb_opp_color.g * 255), (unsigned char)(rgb_opp_color.b * 255)); - opp_color = clr_str.ToStdString(); - - return opp_color; - } - - std::string get_opposite_color(const std::string& color_frst, const std::string& color_scnd) - { - std::string opp_color = ""; - - hsv hsv_frst = rgb2hsv(color_frst); - hsv hsv_scnd = rgb2hsv(color_scnd); - - double delta_h = fabs(hsv_frst.h - hsv_scnd.h); - double start_h = delta_h > 180 ? std::min(hsv_scnd.h, hsv_frst.h) : std::max(hsv_scnd.h, hsv_frst.h); - start_h += 5; // to avoid circle change of colors for 120 deg - if (delta_h < 180) - delta_h = 360 - delta_h; - - hsv hsv_opp = hsv{ start_h + 0.5 * delta_h, rand_val(), rand_val() }; - rgb rgb_opp_color = hsv2rgb(hsv_opp); - - wxString clr_str = wxString::Format(wxT("#%02X%02X%02X"), (unsigned char)(rgb_opp_color.r * 255), (unsigned char)(rgb_opp_color.g * 255), (unsigned char)(rgb_opp_color.b * 255)); - opp_color = clr_str.ToStdString(); - - return opp_color; - } -}; - -#endif \ No newline at end of file diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 8312125978..6d72a5e6f4 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1,3 +1,8 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, David Kocík @kocikdav, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2019 Jason Tibbitts @jasontibbitts +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "ImGuiWrapper.hpp" #include @@ -27,14 +32,18 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "libslic3r/Shape/TextShape.hpp" + #include "3DScene.hpp" #include "GUI.hpp" #include "I18N.hpp" #include "Search.hpp" #include "BitmapCache.hpp" +#include "GUI_App.hpp" #include "../Utils/MacDarkMode.hpp" + #include "nanosvg/nanosvg.h" #include "nanosvg/nanosvgrast.h" #include "OpenGLManager.hpp" @@ -59,6 +68,7 @@ static const std::map font_icons = { #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT {ImGui::SliderFloatEditBtnIcon, "edit_button" }, #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT + {ImGui::ClipboardBtnIcon , "copy_menu" }, {ImGui::CircleButtonIcon , "circle_paint" }, {ImGui::TriangleButtonIcon , "triangle_paint" }, {ImGui::FillButtonIcon , "fill_paint" }, @@ -75,6 +85,7 @@ static const std::map font_icons = { {ImGui::PreferencesDarkButton , "notification_preferences_dark" }, {ImGui::PreferencesHoverDarkButton , "notification_preferences_hover_dark"}, + {ImGui::ClipboardBtnDarkIcon , "copy_menu_dark" }, {ImGui::CircleButtonDarkIcon , "circle_paint_dark" }, {ImGui::TriangleButtonDarkIcon , "triangle_paint_dark" }, {ImGui::FillButtonDarkIcon , "fill_paint_dark" }, @@ -127,11 +138,11 @@ static const std::map font_icons_extra_large = { const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = to_ImVec4(ColorRGBA::ORANGE()); const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.1f, 0.1f, 0.1f, 0.8f }; const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = COL_ORANGE_DARK; const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = COL_ORANGE_LIGHT; -const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED; +const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = COL_BUTTON_HOVERED; //BBS @@ -144,6 +155,7 @@ const ImVec4 ImGuiWrapper::COL_SEPARATOR_DARK = { 0.24f, 0.24f, 0.27f, 1.0f } const ImVec4 ImGuiWrapper::COL_TITLE_BG = { 0.745f, 0.745f, 0.745f, 1.0f }; const ImVec4 ImGuiWrapper::COL_WINDOW_BG = { 1.000f, 1.000f, 1.000f, 1.0f }; const ImVec4 ImGuiWrapper::COL_WINDOW_BG_DARK = { 45 / 255.f, 45 / 255.f, 49 / 255.f, 1.f }; +const ImVec4 ImGuiWrapper::COL_ORCA = to_ImVec4(ColorRGBA::ORCA()); int ImGuiWrapper::TOOLBAR_WINDOW_FLAGS = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove @@ -499,10 +511,12 @@ void ImGuiWrapper::render() m_new_frame_open = false; } -ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) const +ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, + bool hide_text_after_double_hash, + float wrap_width) const { auto text_utf8 = into_u8(text); - ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width); + ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, hide_text_after_double_hash, wrap_width); /*#ifdef __linux__ size.x *= m_style_scaling; @@ -761,16 +775,30 @@ void ImGuiWrapper::end() ImGui::End(); } -bool ImGuiWrapper::button(const wxString &label) +bool ImGuiWrapper::button(const wxString &label, const wxString& tooltip) { auto label_utf8 = into_u8(label); - return ImGui::Button(label_utf8.c_str()); + const bool ret = ImGui::Button(label_utf8.c_str()); + + if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { + auto tooltip_utf8 = into_u8(tooltip); + ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + } + + return ret; } -bool ImGuiWrapper::bbl_button(const wxString &label) +bool ImGuiWrapper::bbl_button(const wxString &label, const wxString& tooltip) { auto label_utf8 = into_u8(label); - return ImGui::BBLButton(label_utf8.c_str()); + const bool ret = ImGui::BBLButton(label_utf8.c_str()); + + if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { + auto tooltip_utf8 = into_u8(tooltip); + ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + } + + return ret; } bool ImGuiWrapper::button(const wxString& label, float width, float height) @@ -779,17 +807,23 @@ bool ImGuiWrapper::button(const wxString& label, float width, float height) return ImGui::Button(label_utf8.c_str(), ImVec2(width, height)); } +bool ImGuiWrapper::button(const wxString& label, const ImVec2 &size, bool enable) +{ + disabled_begin(!enable); + + auto label_utf8 = into_u8(label); + bool res = ImGui::Button(label_utf8.c_str(), size); + + disabled_end(); + return (enable) ? res : false; +} + bool ImGuiWrapper::radio_button(const wxString &label, bool active) { auto label_utf8 = into_u8(label); return ImGui::RadioButton(label_utf8.c_str(), active); } -bool ImGuiWrapper::image_button() -{ - return false; -} - bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal); @@ -958,6 +992,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float m_last_slider_status.edited = ImGui::IsItemEdited(); m_last_slider_status.clicked = ImGui::IsItemClicked(); m_last_slider_status.deactivated_after_edit = ImGui::IsItemDeactivatedAfterEdit(); + if (!m_last_slider_status.can_take_snapshot) + m_last_slider_status.can_take_snapshot = ImGui::IsItemClicked(); if (!tooltip.empty() && ImGui::IsItemHovered()) this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width); @@ -1031,25 +1067,99 @@ bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, fl } #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT -bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection) +static bool image_button_ex(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + ImGui::ItemSize(bb); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, ImGui::GetColorU32(bg_col)); + window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, ImGui::GetColorU32(tint_col)); + + return pressed; +} + +bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + ImGui::PushID((void*)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + ImGui::PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; + return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags); +} + +bool ImGuiWrapper::image_button(const wchar_t icon, const wxString& tooltip) +{ + const ImGuiIO& io = ImGui::GetIO(); + const ImTextureID tex_id = io.Fonts->TexID; + assert(io.Fonts->TexWidth > 0 && io.Fonts->TexHeight > 0); + const float inv_tex_w = 1.0f / float(io.Fonts->TexWidth); + const float inv_tex_h = 1.0f / float(io.Fonts->TexHeight); + const ImFontAtlasCustomRect* const rect = GetTextureCustomRect(icon); + const ImVec2 size = { float(rect->Width), float(rect->Height) }; + const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h); + const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h); + ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.25f, 0.25f, 0.25f, 1.0f }); + const bool res = image_button(tex_id, size, uv0, uv1); + ImGui::PopStyleColor(3); + + if (!tooltip.empty() && ImGui::IsItemHovered()) + this->tooltip(tooltip, ImGui::GetFontSize() * 20.0f); + + return res; +} + +bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/) +{ + return combo(into_u8(label), options, selection, flags, label_width, item_width); +} + +bool ImGuiWrapper::combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/) { // this is to force the label to the left of the widget: - text(label); - ImGui::SameLine(); + const bool hidden_label = boost::starts_with(label, "##"); + if (!label.empty() && !hidden_label) { + text(label); + ImGui::SameLine(label_width); + } + ImGui::PushItemWidth(item_width); int selection_out = selection; bool res = false; const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : ""; - if (ImGui::BeginCombo("", selection_str)) { + if (ImGui::BeginCombo(hidden_label ? label.c_str() : ("##" + label).c_str(), selection_str, flags)) { for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; + res = true; } } ImGui::EndCombo(); - res = true; } selection = selection_out; @@ -1790,6 +1900,37 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } +ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id) +{ + auto item = m_custom_glyph_rects_ids.find(tex_id); + return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr; +} + +void ImGuiWrapper::disable_background_fadeout_animation() +{ + GImGui->DimBgRatio = 1.0f; +} + +ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA& color) +{ + return ImGui::GetColorU32({ color.r(), color.g(), color.b(), color.a() }); +} + +ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGBA& color) +{ + return { color.r(), color.g(), color.b(), color.a() }; +} + +ColorRGBA ImGuiWrapper::from_ImU32(const ImU32& color) +{ + return from_ImVec4(ImGui::ColorConvertU32ToFloat4(color)); +} + +ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4& color) +{ + return { color.x, color.y, color.z, color.w }; +} + #ifdef __APPLE__ static const ImWchar ranges_keyboard_shortcuts[] = { @@ -2141,12 +2282,18 @@ void ImGuiWrapper::init_font(bool compress) int rect_id = io.Fonts->CustomRects.Size; // id of the rectangle added next // add rectangles for the icons to the font atlas - for (auto& icon : font_icons) + for (auto& icon : font_icons) { + m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); - for (auto& icon : font_icons_large) + } + for (auto& icon : font_icons_large) { + m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); - for (auto& icon : font_icons_extra_large) + } + for (auto& icon : font_icons_extra_large) { + m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); + } // Build texture atlas unsigned char* pixels; @@ -2154,55 +2301,37 @@ void ImGuiWrapper::init_font(bool compress) io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. BOOST_LOG_TRIVIAL(trace) << "Build default font texture done. width: " << width << ", height: " << height; - // Fill rectangles from the SVG-icons - for (auto icon : font_icons) { + auto load_icon_from_svg = [this, &io, pixels, width, &rect_id](const std::pair icon, int icon_sz) { if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) { assert(rect->Width == icon_sz); assert(rect->Height == icon_sz); - unsigned outwidth, outheight; + unsigned outwidth, outheight; std::vector raw_data = load_svg(icon.second, icon_sz, icon_sz, &outwidth, &outheight); - const ImU32* pIn = (ImU32*)raw_data.data(); - for (unsigned y = 0; y < outheight; y++) { - ImU32* pOut = (ImU32*)pixels + (rect->Y + y) * width + (rect->X); - for (unsigned x = 0; x < outwidth; x++) - *pOut++ = *pIn++; + if (!raw_data.empty()) { + const ImU32* pIn = (ImU32*)raw_data.data(); + for (unsigned y = 0; y < outheight; y++) { + ImU32* pOut = (ImU32*)pixels + (rect->Y + y) * width + (rect->X); + for (unsigned x = 0; x < outwidth; x++) + *pOut++ = *pIn++; + } } } rect_id++; + }; + + // Fill rectangles from the SVG-icons + for (auto icon : font_icons) { + load_icon_from_svg(icon, icon_sz); } icon_sz *= 2; // default size of large icon is 32 px for (auto icon : font_icons_large) { - if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) { - assert(rect->Width == icon_sz); - assert(rect->Height == icon_sz); - unsigned outwidth, outheight; - std::vector raw_data = load_svg(icon.second, icon_sz, icon_sz, &outwidth, &outheight); - const ImU32* pIn = (ImU32*)raw_data.data(); - for (unsigned y = 0; y < outheight; y++) { - ImU32* pOut = (ImU32*)pixels + (rect->Y + y) * width + (rect->X); - for (unsigned x = 0; x < outwidth; x++) - *pOut++ = *pIn++; - } - } - rect_id++; + load_icon_from_svg(icon, icon_sz); } icon_sz *= 2; // default size of extra large icon is 64 px for (auto icon : font_icons_extra_large) { - if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) { - assert(rect->Width == icon_sz); - assert(rect->Height == icon_sz); - unsigned outwidth, outheight; - std::vector raw_data = load_svg(icon.second, icon_sz, icon_sz, &outwidth, &outheight); - const ImU32* pIn = (ImU32*)raw_data.data(); - for (unsigned y = 0; y < outheight; y++) { - ImU32* pOut = (ImU32*)pixels + (rect->Y + y) * width + (rect->X); - for (unsigned x = 0; x < outwidth; x++) - *pOut++ = *pIn++; - } - } - rect_id++; + load_icon_from_svg(icon, icon_sz); } // Upload texture to graphics system @@ -2379,100 +2508,147 @@ void ImGuiWrapper::init_style() void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) { + if (draw_data == nullptr || draw_data->CmdListsCount == 0) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("imgui"); + if (shader == nullptr) + return; + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) ImGuiIO& io = ImGui::GetIO(); - int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); + const int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); + const int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); if (fb_width == 0 || fb_height == 0) return; - draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + shader->start_using(); // We are using the OpenGL fixed pipeline to make the example code simpler to read! // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. - GLint last_texture; glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); - GLint last_polygon_mode[2]; glsafe(::glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode)); - GLint last_viewport[4]; glsafe(::glGetIntegerv(GL_VIEWPORT, last_viewport)); - GLint last_scissor_box[4]; glsafe(::glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box)); + GLint last_texture; glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + GLint last_polygon_mode[2]; glsafe(::glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode)); + GLint last_viewport[4]; glsafe(::glGetIntegerv(GL_VIEWPORT, last_viewport)); + GLint last_scissor_box[4]; glsafe(::glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box)); + GLint last_texture_env_mode; glsafe(::glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &last_texture_env_mode)); glsafe(::glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_LIGHTING)); - glsafe(::glDisable(GL_COLOR_MATERIAL)); glsafe(::glEnable(GL_SCISSOR_TEST)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY)); - glsafe(::glEnableClientState(GL_COLOR_ARRAY)); glsafe(::glEnable(GL_TEXTURE_2D)); glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)); - GLint texture_env_mode = GL_MODULATE; - glsafe(::glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texture_env_mode)); - glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)); - //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound // Setup viewport, orthographic projection matrix - // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. glsafe(::glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height)); - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - glsafe(::glOrtho(draw_data->DisplayPos.x, draw_data->DisplayPos.x + draw_data->DisplaySize.x, draw_data->DisplayPos.y + draw_data->DisplaySize.y, draw_data->DisplayPos.y, -1.0f, +1.0f)); - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); + const float L = draw_data->DisplayPos.x; + const float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + const float T = draw_data->DisplayPos.y; + const float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + + Matrix4f ortho_projection; + ortho_projection << + 2.0f / (R - L), 0.0f, 0.0f, (R + L) / (L - R), + 0.0f, 2.0f / (T - B), 0.0f, (T + B) / (B - T), + 0.0f, 0.0f, -1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f; + + shader->set_uniform("Texture", 0); + shader->set_uniform("ProjMtx", ortho_projection); + + // Will project scissor/clipping rectangles into framebuffer space + const ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + const ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Render command lists - ImVec2 pos = draw_data->DisplayPos; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { + for (int n = 0; n < draw_data->CmdListsCount; ++n) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; - const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; - glsafe(::glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos)))); - glsafe(::glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv)))); - glsafe(::glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col)))); + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert); + const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx); - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { + GLuint vbo_id; + glsafe(::glGenBuffers(1, &vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, vtx_buffer, GL_STATIC_DRAW)); + + GLuint ibo_id; + glsafe(::glGenBuffers(1, &ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, idx_buffer, GL_STATIC_DRAW)); + + const int position_id = shader->get_attrib_location("Position"); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (const void*)IM_OFFSETOF(ImDrawVert, pos))); + glsafe(::glEnableVertexAttribArray(position_id)); + } + const int uv_id = shader->get_attrib_location("UV"); + if (uv_id != -1) { + glsafe(::glVertexAttribPointer(uv_id, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (const void*)IM_OFFSETOF(ImDrawVert, uv))); + glsafe(::glEnableVertexAttribArray(uv_id)); + } + const int color_id = shader->get_attrib_location("Color"); + if (color_id != -1) { + glsafe(::glVertexAttribPointer(color_id, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (const void*)IM_OFFSETOF(ImDrawVert, col))); + glsafe(::glEnableVertexAttribArray(color_id)); + } + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; ++cmd_i) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback) - { // User callback (registered via ImDrawList::AddCallback) pcmd->UserCallback(cmd_list, pcmd); - } - else - { - ImVec4 clip_rect = ImVec4(pcmd->ClipRect.x - pos.x, pcmd->ClipRect.y - pos.y, pcmd->ClipRect.z - pos.x, pcmd->ClipRect.w - pos.y); - if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) - { - // Apply scissor/clipping rectangle - glsafe(::glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y))); + else { + // Project scissor/clipping rectangles into framebuffer space + const ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + const ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; - // Bind texture, Draw - glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId)); - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer)); - } + // Apply scissor/clipping rectangle (Y is inverted in OpenGL) + glsafe(::glScissor((int)clip_min.x, (int)(fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y))); + + // Bind texture, Draw + glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID())); + glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)))); } - idx_buffer += pcmd->ElemCount; } + + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); + if (uv_id != -1) + glsafe(::glDisableVertexAttribArray(uv_id)); + if (color_id != -1) + glsafe(::glDisableVertexAttribArray(color_id)); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + glsafe(::glDeleteBuffers(1, &ibo_id)); + glsafe(::glDeleteBuffers(1, &vbo_id)); } // Restore modified state - glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texture_env_mode)); - glsafe(::glDisableClientState(GL_COLOR_ARRAY)); - glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_texture_env_mode)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture)); - glsafe(::glMatrixMode(GL_MODELVIEW)); - glsafe(::glPopMatrix()); - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glPopMatrix()); glsafe(::glPopAttrib()); - glsafe(::glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1])); + glsafe(::glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); + glsafe(::glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]))); glsafe(::glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3])); glsafe(::glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3])); + + shader->stop_using(); + + if (curr_shader != nullptr) + curr_shader->start_using(); } bool ImGuiWrapper::display_initialized() const diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 11146f73a8..58e42f1996 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, David Kocík @kocikdav, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_ImGuiWrapper_hpp_ #define slic3r_ImGuiWrapper_hpp_ @@ -11,9 +15,12 @@ #include "libslic3r/Point.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" -namespace Slic3r {namespace Search { +namespace Slic3r { +class ColorRGBA; +namespace Search { struct OptionViewParameters; -}} +} // namespace Search +} // namespace Slic3r class wxString; class wxMouseEvent; @@ -58,6 +65,7 @@ class ImGuiWrapper #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT bool m_requires_extra_frame{ false }; #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT + std::map m_custom_glyph_rects_ids; std::string m_clipboard_text; public: @@ -66,6 +74,11 @@ public: bool edited { false }; bool clicked { false }; bool deactivated_after_edit { false }; + // flag to indicate possibility to take snapshot from the slider value + // It's used from Gizmos to take snapshots just from the very beginning of the editing + bool can_take_snapshot { false }; + // When Undo/Redo snapshot is taken, then call this function + void invalidate_snapshot() { can_take_snapshot = false; } }; ImGuiWrapper(); @@ -87,12 +100,13 @@ public: float scaled(float x) const { return x * m_font_size; } ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } - ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f) const; + ImVec2 calc_text_size(const wxString &text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f) const; ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const; ImVec2 get_item_spacing() const; float get_slider_float_height() const; const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; } + LastSliderStatus& get_last_slider_status() { return m_last_slider_status; } void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); @@ -110,11 +124,11 @@ public: bool begin(const wxString& name, bool* close, int flags = 0); void end(); - bool button(const wxString &label); - bool bbl_button(const wxString &label); - bool button(const wxString& label, float width, float height); + bool button(const wxString &label, const wxString& tooltip = {}); + bool bbl_button(const wxString &label, const wxString& tooltip = {}); + bool button(const wxString& label, float width, float height); + bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f) bool radio_button(const wxString &label, bool active); - bool image_button(); bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); @@ -147,7 +161,12 @@ public: bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true); #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT - bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected + bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0); + bool image_button(const wchar_t icon, const wxString& tooltip = L""); + + // Use selection = -1 to not mark any option as selected + bool combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f); + bool combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f); bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel); void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str, Search::OptionViewParameters &view_params, @@ -182,6 +201,15 @@ public: void reset_requires_extra_frame() { m_requires_extra_frame = false; } #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT + void disable_background_fadeout_animation(); + + static ImU32 to_ImU32(const ColorRGBA& color); + static ImVec4 to_ImVec4(const ColorRGBA& color); + static ColorRGBA from_ImU32(const ImU32& color); + static ColorRGBA from_ImVec4(const ImVec4& color); + + ImFontAtlasCustomRect* GetTextureCustomRect(const wchar_t& tex_id); + static const ImVec4 COL_GREY_DARK; static const ImVec4 COL_GREY_LIGHT; static const ImVec4 COL_ORANGE_DARK; @@ -201,6 +229,7 @@ public: static const ImVec4 COL_WINDOW_BG_DARK; static const ImVec4 COL_SEPARATOR; static const ImVec4 COL_SEPARATOR_DARK; + static const ImVec4 COL_ORCA; //BBS static void on_change_color_mode(bool is_dark); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index cd9a617f4b..18a86fb8ff 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "MeshUtils.hpp" #include "libslic3r/Tesselate.hpp" @@ -5,23 +9,39 @@ #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/CSGMesh/SliceCSGMesh.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/CameraUtils.hpp" + #include #include -#include "CameraUtils.hpp" + +#include namespace Slic3r { namespace GUI { +void MeshClipper::set_behaviour(bool fill_cut, double contour_width) +{ + if (fill_cut != m_fill_cut || ! is_approx(contour_width, m_contour_width)) + m_result.reset(); + m_fill_cut = fill_cut; + m_contour_width = contour_width; +} + + + void MeshClipper::set_plane(const ClippingPlane& plane) { if (m_plane != plane) { m_plane = plane; - m_triangles_valid = false; + m_result.reset(); } } @@ -30,27 +50,41 @@ void MeshClipper::set_limiting_plane(const ClippingPlane& plane) { if (m_limiting_plane != plane) { m_limiting_plane = plane; - m_triangles_valid = false; + m_result.reset(); } } -void MeshClipper::set_mesh(const TriangleMesh& mesh) +void MeshClipper::set_mesh(const indexed_triangle_set& mesh) { - if (m_mesh != &mesh) { + if (m_mesh.get() != &mesh) { m_mesh = &mesh; - m_triangles_valid = false; - m_triangles2d.resize(0); + m_result.reset(); } } -void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) +void MeshClipper::set_mesh(AnyPtr &&ptr) { - if (m_negative_mesh != &mesh) { + if (m_mesh.get() != ptr.get()) { + m_mesh = std::move(ptr); + m_result.reset(); + } +} + +void MeshClipper::set_negative_mesh(const indexed_triangle_set& mesh) +{ + if (m_negative_mesh.get() != &mesh) { m_negative_mesh = &mesh; - m_triangles_valid = false; - m_triangles2d.resize(0); + m_result.reset(); + } +} + +void MeshClipper::set_negative_mesh(AnyPtr &&ptr) +{ + if (m_negative_mesh.get() != ptr.get()) { + m_negative_mesh = std::move(ptr); + m_result.reset(); } } @@ -60,61 +94,157 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo) { if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { m_trafo = trafo; - m_triangles_valid = false; - m_triangles2d.resize(0); + m_result.reset(); } } - - -void MeshClipper::render_cut() +void MeshClipper::render_cut(const ColorRGBA& color, const std::vector* ignore_idxs) { - if (! m_triangles_valid) + if (! m_result) recalculate_triangles(); + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); - if (m_vertex_array.has_VBOs()) - m_vertex_array.render(); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + for (size_t i=0; icut_islands.size(); ++i) { + if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) + continue; + CutIsland& isl = m_result->cut_islands[i]; + isl.model.set_color(isl.disabled ? ColorRGBA(0.5f, 0.5f, 0.5f, 1.f) : color); + isl.model.render(); + } + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); } -bool MeshClipper::is_projection_inside_cut(const Vec3d &point_in) const + +void MeshClipper::render_contour(const ColorRGBA& color, const std::vector* ignore_idxs) +{ + if (! m_result) + recalculate_triangles(); + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + for (size_t i=0; icut_islands.size(); ++i) { + if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) + continue; + CutIsland& isl = m_result->cut_islands[i]; + isl.model_expanded.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color); + isl.model_expanded.render(); + } + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +} + +int MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const { if (!m_result || m_result->cut_islands.empty()) - return false; + return -1; Vec3d point = m_result->trafo.inverse() * point_in; Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y())); - for (const CutIsland &isl : m_result->cut_islands) { + for (int i=0; icut_islands.size()); ++i) { + const CutIsland& isl = m_result->cut_islands[i]; if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) - return true; + return i; // TODO: handle intersecting contours } - return false; + return -1; } bool MeshClipper::has_valid_contour() const { - return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland &isl) { return !isl.expoly.empty(); }); + return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); }); } +std::vector MeshClipper::point_per_contour() const +{ + assert(m_result); + std::vector out; + + for (const CutIsland& isl : m_result->cut_islands) { + assert(isl.expoly.contour.size() > 2); + // Now return a point lying inside the contour but not in a hole. + // We do this by taking a point lying close to the edge, repeating + // this several times for different edges and distances from them. + // (We prefer point not extremely close to the border. + bool done = false; + Vec2d p; + size_t i = 1; + while (i < isl.expoly.contour.size()) { + const Vec2d& a = unscale(isl.expoly.contour.points[i-1]); + const Vec2d& b = unscale(isl.expoly.contour.points[i]); + Vec2d n = (b-a).normalized(); + std::swap(n.x(), n.y()); + n.x() = -1 * n.x(); + double f = 10.; + while (f > 0.05) { + p = (0.5*(b+a)) + f * n; + if (isl.expoly.contains(Point::new_scale(p))) { + done = true; + break; + } + f = f/10.; + } + if (done) + break; + i += std::max(size_t(2), isl.expoly.contour.size() / 5); + } + // If the above failed, just return the centroid, regardless of whether + // it is inside the contour or in a hole (we must return something). + Vec2d c = done ? p : unscale(isl.expoly.contour.centroid()); + out.emplace_back(m_result->trafo * Vec3d(c.x(), c.y(), 0.)); + } + return out; +} + + void MeshClipper::recalculate_triangles() { - const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); - // Calculate clipping plane normal in mesh coordinates. - const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); - const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); - // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). - const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); + m_result = ClipResult(); + + auto plane_mesh = Eigen::Hyperplane(m_plane.get_normal(), -m_plane.distance(Vec3d::Zero())).transform(m_trafo.get_matrix().inverse()); + const Vec3d up = plane_mesh.normal(); + const float height_mesh = -plane_mesh.offset(); // Now do the cutting MeshSlicingParams slicing_params; slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); - ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); + ExPolygons expolys; - if (m_negative_mesh && !m_negative_mesh->empty()) { - const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params)); - expolys = diff_ex(expolys, neg_expolys); + if (m_csgmesh.empty()) { + if (m_mesh) + expolys = union_ex(slice_mesh(*m_mesh, height_mesh, slicing_params)); + + if (m_negative_mesh && !m_negative_mesh->empty()) { + const ExPolygons neg_expolys = union_ex(slice_mesh(*m_negative_mesh, height_mesh, slicing_params)); + expolys = diff_ex(expolys, neg_expolys); + } + } else { + expolys = std::move(csg::slice_csgmesh_ex(range(m_csgmesh), {height_mesh}, MeshSlicingParamsEx{slicing_params}).front()); } + // Triangulate and rotate the cut into world coords: Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), up); @@ -122,7 +252,6 @@ void MeshClipper::recalculate_triangles() tr.rotate(q); tr = m_trafo.get_matrix() * tr; - m_result = ClipResult(); m_result->trafo = tr; if (m_limiting_plane != ClippingPlane::ClipsNothing()) @@ -170,7 +299,7 @@ void MeshClipper::recalculate_triangles() // it so it lies on our line. This will be the figure to subtract // from the cut. The coordinates must not overflow after the transform, // make the rectangle a bit smaller. - const coord_t size = (std::numeric_limits::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4; + const coord_t size = (std::numeric_limits::max()/2 - scale_(std::max(std::abs(e * a), std::abs(e * b)))) / 4; Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})}; ep.front().rotate(angle); ep.front().translate(scale_(-e * a), scale_(-e * b)); @@ -178,28 +307,107 @@ void MeshClipper::recalculate_triangles() } } - for (const ExPolygon &exp : expolys) { - m_result->cut_islands.push_back(CutIsland()); - CutIsland &isl = m_result->cut_islands.back(); - isl.expoly = std::move(exp); - isl.expoly_bb = get_extents(exp); - } - - m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); - tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting + Transform3d tr2 = tr; + tr2.pretranslate(0.002 * m_plane.get_normal().normalized()); - m_vertex_array.release_geometry(); - for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { - m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); - const size_t idx = it - m_triangles2d.cbegin(); - m_vertex_array.push_triangle(idx, idx+1, idx+2); + + std::vector triangles2d; + + for (const ExPolygon& exp : expolys) { + triangles2d.clear(); + + m_result->cut_islands.push_back(CutIsland()); + CutIsland& isl = m_result->cut_islands.back(); + + if (m_fill_cut) { + triangles2d = triangulate_expolygon_2f(exp, m_trafo.get_matrix().matrix().determinant() < 0.); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(triangles2d.size()); + init_data.reserve_indices(triangles2d.size()); + + // vertices + indices + for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - triangles2d.cbegin(); + init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + } + + if (!init_data.is_empty()) + isl.model.init_from(std::move(init_data)); + } + + if (m_contour_width != 0. && ! exp.contour.empty()) { + triangles2d.clear(); + + // The contours must not scale with the object. Check the scale factor + // in the respective directions, create a scaled copy of the ExPolygon + // offset it and then unscale the result again. + + Transform3d t = tr; + t.translation() = Vec3d::Zero(); + double scale_x = (t * Vec3d::UnitX()).norm(); + double scale_y = (t * Vec3d::UnitY()).norm(); + + // To prevent overflow after scaling, downscale the input if needed: + double extra_scale = 1.; + int32_t limit = int32_t(std::min(std::numeric_limits::max() / (2. * std::max(1., scale_x)), std::numeric_limits::max() / (2. * std::max(1., scale_y)))); + int32_t max_coord = 0; + for (const Point& pt : exp.contour) + max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y()))); + if (max_coord + m_contour_width >= limit) + extra_scale = 0.9 * double(limit) / max_coord; + + ExPolygon exp_copy = exp; + if (extra_scale != 1.) + exp_copy.scale(extra_scale); + exp_copy.scale(scale_x, scale_y); + + ExPolygons expolys_exp = offset_ex(exp_copy, scale_(m_contour_width)); + expolys_exp = diff_ex(expolys_exp, ExPolygons({exp_copy})); + + for (ExPolygon& e : expolys_exp) { + e.scale(1./scale_x, 1./scale_y); + if (extra_scale != 1.) + e.scale(1./extra_scale); + } + + + triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.); + GLModel::Geometry init_data = GLModel::Geometry(); + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(triangles2d.size()); + init_data.reserve_indices(triangles2d.size()); + + // vertices + indices + for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - triangles2d.cbegin(); + init_data.add_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); + } + + if (!init_data.is_empty()) + isl.model_expanded.init_from(std::move(init_data)); + } + + isl.expoly = std::move(exp); + isl.expoly_bb = get_extents(isl.expoly); + + Point centroid_scaled = isl.expoly.contour.centroid(); + Vec3d centroid_world = m_result->trafo * Vec3d(unscale(centroid_scaled).x(), unscale(centroid_scaled).y(), 0.); + isl.hash = isl.expoly.contour.size() + size_t(std::abs(100.*centroid_world.x())) + size_t(std::abs(100.*centroid_world.y())) + size_t(std::abs(100.*centroid_world.z())); } - m_vertex_array.finalize_geometry(true); - m_triangles_valid = true; + // Now sort the islands so they are in defined order. This is a hack needed by cut gizmo, which sometimes + // flips the normal of the cut, in which case the contours stay the same but their order may change. + std::sort(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& a, const CutIsland& b) { + return a.hash < b.hash; + }); } @@ -208,46 +416,26 @@ Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const return m_normals[facet_idx]; } -void MeshRaycaster::line_from_mouse_pos_static(const Vec2d &mouse_pos, const Transform3d &trafo, const Camera &camera, Vec3d &point, Vec3d &direction) +void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction) { CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); Transform3d inv = trafo.inverse(); - point = inv * point; - direction = inv.linear() * direction; + point = inv*point; + direction = inv.linear()*direction; } -void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) const -{ - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - Vec3d pt1; - Vec3d pt2; - igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 0.), - modelview, projection, viewport, pt1); - igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.), - modelview, projection, viewport, pt2); - - Transform3d inv = trafo.inverse(); - pt1 = inv * pt1; - pt2 = inv * pt2; - - point = pt1; - direction = pt2-pt1; -} - - bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, size_t* facet_idx, bool sinking_limit) const { Vec3d point; Vec3d direction; - line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); + Transform3d inv = trafo.inverse(); + point = inv*point; + direction = inv.linear()*direction; - std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector hits = m_emesh.query_ray_hits(point, direction); if (hits.empty()) return false; // no intersection found @@ -280,6 +468,21 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& } + +bool MeshRaycaster::intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const +{ + Transform3d trafo_inv = trafo.inverse(); + Vec3d to = trafo_inv * (point + direction); + point = trafo_inv * point; + direction = (to-point).normalized(); + + std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector neg_hits = m_emesh.query_ray_hits(point, -direction); + + return !hits.empty() || !neg_hits.empty(); +} + + std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, const ClippingPlane* clipping_plane) const { @@ -298,7 +501,7 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; + std::vector hits; // Offset the start of the ray by EPSILON to account for numerical inaccuracies. hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast() + direction_to_camera_mesh * EPSILON), direction_to_camera_mesh); @@ -328,13 +531,47 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo return out; } +bool MeshRaycaster::closest_hit(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, size_t* facet_idx) const +{ + Vec3d point; + Vec3d direction; + line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); + + const std::vector hits = m_emesh.query_ray_hits(point, direction.normalized()); + + if (hits.empty()) + return false; // no intersection found + + size_t hit_id = 0; + if (clipping_plane != nullptr) { + while (hit_id < hits.size() && clipping_plane->is_point_clipped(trafo * hits[hit_id].position())) { + ++hit_id; + } + } + + if (hit_id == hits.size()) + return false; // all points are obscured or cut by the clipping plane. + + const AABBMesh::hit_result& hit = hits[hit_id]; + + position = hit.position().cast(); + normal = hit.normal().cast(); + + if (facet_idx != nullptr) + *facet_idx = hit.face(); + + return true; +} Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const { int idx = 0; Vec3d closest_point; - m_emesh.squared_distance(point.cast(), idx, closest_point); + Vec3d pointd = point.cast(); + m_emesh.squared_distance(pointd, idx, closest_point); if (normal) + // TODO: consider: get_normal(m_emesh, pointd).cast(); *normal = m_normals[idx]; return closest_point.cast(); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 123b8a7852..c3e0b4247d 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -1,17 +1,25 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Tomáš Mészáros @tamasmeszaros, Enrico Turri @enricoturri1966, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_MeshUtils_hpp_ #define slic3r_MeshUtils_hpp_ #include "libslic3r/Point.hpp" #include "libslic3r/Geometry.hpp" -#include "libslic3r/SLA/IndexedMesh.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/AABBMesh.hpp" +#include "libslic3r/CSGMesh/TriangleMeshAdapter.hpp" +#include "libslic3r/CSGMesh/CSGMeshCopy.hpp" #include "admesh/stl.h" -#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/GLModel.hpp" #include +#include +#include namespace Slic3r { - namespace GUI { struct Camera; @@ -20,16 +28,14 @@ struct Camera; // lm_FIXME: Following class might possibly be replaced by Eigen::Hyperplane class ClippingPlane { - double m_data[4]; + std::array m_data; public: - ClippingPlane() - { + ClippingPlane() { *this = ClipsNothing(); } - ClippingPlane(const Vec3d& direction, double offset) - { + ClippingPlane(const Vec3d& direction, double offset) { set_normal(direction); set_offset(offset); } @@ -45,8 +51,7 @@ public: } bool is_point_clipped(const Vec3d& point) const { return distance(point) < 0.; } - void set_normal(const Vec3d& normal) - { + void set_normal(const Vec3d& normal) { const Vec3d norm_dir = normal.normalized(); m_data[0] = norm_dir.x(); m_data[1] = norm_dir.y(); @@ -55,22 +60,28 @@ public: void set_offset(double offset) { m_data[3] = offset; } double get_offset() const { return m_data[3]; } Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); } + void invert_normal() { m_data[0] *= -1.0; m_data[1] *= -1.0; m_data[2] *= -1.0; } + ClippingPlane inverted_normal() const { return ClippingPlane(-get_normal(), get_offset()); } bool is_active() const { return m_data[3] != DBL_MAX; } static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); } - const double* get_data() const { return m_data; } + const std::array& get_data() const { return m_data; } // Serialization through cereal library template - void serialize( Archive & ar ) - { + void serialize( Archive & ar ) { ar( m_data[0], m_data[1], m_data[2], m_data[3] ); } }; // MeshClipper class cuts a mesh and is able to return a triangulated cut. -class MeshClipper { +class MeshClipper +{ public: + // Set whether the cut should be triangulated and whether a cut + // contour should be calculated and shown. + void set_behaviour(bool fill_cut, double contour_width); + // Inform MeshClipper about which plane we want to use to cut the mesh // This is supposed to be in world coordinates. void set_plane(const ClippingPlane& plane); @@ -82,9 +93,25 @@ public: // Which mesh to cut. MeshClipper remembers const * to it, caller // must make sure that it stays valid. - void set_mesh(const TriangleMesh& mesh); + void set_mesh(const indexed_triangle_set& mesh); + void set_mesh(AnyPtr &&ptr); - void set_negative_mesh(const TriangleMesh &mesh); + void set_negative_mesh(const indexed_triangle_set &mesh); + void set_negative_mesh(AnyPtr &&ptr); + + template + void set_mesh(const Range &csgrange, bool copy_meshes = false) + { + if (! csg::is_same(range(m_csgmesh), csgrange)) { + m_csgmesh.clear(); + if (copy_meshes) + csg::copy_csgrange_deep(csgrange, std::back_inserter(m_csgmesh)); + else + csg::copy_csgrange_shallow(csgrange, std::back_inserter(m_csgmesh)); + + m_result.reset(); + } + } // Inform the MeshClipper about the transformation that transforms the mesh // into world coordinates. @@ -92,34 +119,41 @@ public: // Render the triangulated cut. Transformation matrices should // be set in world coords. - void render_cut(); + void render_cut(const ColorRGBA& color, const std::vector* ignore_idxs = nullptr); + void render_contour(const ColorRGBA& color, const std::vector* ignore_idxs = nullptr); - bool is_projection_inside_cut(const Vec3d &point) const; + // Returns index of the contour which was clicked, -1 otherwise. + int is_projection_inside_cut(const Vec3d& point) const; bool has_valid_contour() const; + int get_number_of_contours() const { return m_result ? m_result->cut_islands.size() : 0; } + std::vector point_per_contour() const; private: void recalculate_triangles(); Geometry::Transformation m_trafo; - const TriangleMesh* m_mesh = nullptr; - const TriangleMesh* m_negative_mesh = nullptr; + AnyPtr m_mesh; + AnyPtr m_negative_mesh; + std::vector m_csgmesh; + ClippingPlane m_plane; ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing(); - std::vector m_triangles2d; - GLIndexedVertexArray m_vertex_array; - bool m_triangles_valid = false; - struct CutIsland - { - ExPolygon expoly; + struct CutIsland { + GLModel model; + GLModel model_expanded; + ExPolygon expoly; BoundingBox expoly_bb; + bool disabled = false; + size_t hash; }; - struct ClipResult - { + struct ClipResult { std::vector cut_islands; - Transform3d trafo; // this rotates the cut into world coords + Transform3d trafo; // this rotates the cut into world coords }; - std::optional m_result; // the cut plane + std::optional m_result; + bool m_fill_cut = true; + double m_contour_width = 0.; }; @@ -128,19 +162,21 @@ private: // whether certain points are visible or obscured by the mesh etc. class MeshRaycaster { public: - // The class references extern TriangleMesh, which must stay alive - // during MeshRaycaster existence. - MeshRaycaster(const TriangleMesh& mesh) - : m_emesh(mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length - , m_normals(its_face_normals(mesh.its)) + explicit MeshRaycaster(std::shared_ptr mesh) + : m_mesh(std::move(mesh)) + , m_emesh(*m_mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length + , m_normals(its_face_normals(m_mesh->its)) { + assert(m_mesh); } - static void line_from_mouse_pos_static(const Vec2d &mouse_pos, const Transform3d &trafo, - const Camera &camera, Vec3d &point, Vec3d &direction); + explicit MeshRaycaster(const TriangleMesh &mesh) + : MeshRaycaster(std::make_unique(mesh)) + {} - void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) const; + // DEPRICATED - use CameraUtils::ray_from_screen_pos + static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3d& point, Vec3d& direction); // Given a mouse position, this returns true in case it is on the mesh. bool unproject_on_mesh( @@ -153,6 +189,12 @@ public: size_t* facet_idx = nullptr, // index of the facet hit bool sinking_limit = true ) const; + + const AABBMesh &get_aabb_mesh() const { return m_emesh; } + + // Given a point and direction in world coords, returns whether the respective line + // intersects the mesh if it is transformed into world by trafo. + bool intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const; // Given a vector of points in woorld coordinates, this returns vector // of indices of points that are visible (i.e. not cut by clipping plane @@ -164,10 +206,22 @@ public: const ClippingPlane* clipping_plane = nullptr // clipping plane (if active) ) const; + // Returns true if the ray, built from mouse position and camera direction, intersects the mesh. + // In this case, position and normal contain the position and normal, in model coordinates, of the intersection closest to the camera, + // depending on the position/orientation of the clipping_plane, if specified + bool closest_hit( + const Vec2d& mouse_pos, + const Transform3d& trafo, // how to get the mesh into world coords + const Camera& camera, // current camera position + Vec3f& position, // where to save the positibon of the hit (mesh coords) + Vec3f& normal, // normal of the triangle that was hit + const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active) + size_t* facet_idx = nullptr // index of the facet hit + ) const; + // Given a point in world coords, the method returns closest point on the mesh. // The output is in mesh coords. // normal* can be used to also get normal of the respective triangle. - Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const; // Given a point in mesh coords, the method returns the closest facet from mesh. @@ -176,11 +230,22 @@ public: Vec3f get_triangle_normal(size_t facet_idx) const; private: - sla::IndexedMesh m_emesh; + std::shared_ptr m_mesh; + AABBMesh m_emesh; std::vector m_normals; }; - +struct PickingModel +{ + GLModel model; + std::unique_ptr mesh_raycaster; + + void reset() { + model.reset(); + mesh_raycaster.reset(); + } +}; + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index cceb1b5d88..64060b6958 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "MsgDialog.hpp" #include @@ -14,6 +18,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "I18N.hpp" //#include "ConfigWizard.hpp" @@ -264,9 +269,9 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); wxFont monospace = wxGetApp().code_font(); wxColour text_clr = wxGetApp().get_label_clr_default(); - wxColour bgr_clr = parent->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + wxColour bgr_clr = parent->GetBackgroundColour(); + auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); const int font_size = font.GetPointSize(); int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size }; html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size); @@ -410,6 +415,58 @@ InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& finalize(); } +wxString get_wraped_wxString(const wxString& in, size_t line_len /*=80*/) +{ + wxString out; + + for (size_t i = 0; i < in.size();) { + // Overwrite the character (space or newline) starting at ibreak? + bool overwrite = false; + // UTF8 representation of wxString. + // Where to break the line, index of character at the start of a UTF-8 sequence. + size_t ibreak = size_t(-1); + // Overwrite the character at ibreak (it is a whitespace) or not? + size_t j = i; + for (size_t cnt = 0; j < in.size();) { + if (bool newline = in[j] == '\n'; in[j] == ' ' || in[j] == '\t' || newline) { + // Overwrite the whitespace. + ibreak = j ++; + overwrite = true; + if (newline) + break; + } else if (in[j] == '/' +#ifdef _WIN32 + || in[j] == '\\' +#endif // _WIN32 + ) { + // Insert after the slash. + ibreak = ++ j; + overwrite = false; + } else + j += get_utf8_sequence_length(in.c_str() + j, in.size() - j); + if (++ cnt == line_len) { + if (ibreak == size_t(-1)) { + ibreak = j; + overwrite = false; + } + break; + } + } + if (j == in.size()) { + out.append(in.begin() + i, in.end()); + break; + } + assert(ibreak != size_t(-1)); + out.append(in.begin() + i, in.begin() + ibreak); + out.append('\n'); + i = ibreak; + if (overwrite) + ++ i; + } + + return out; +} + // InfoDialog DownloadDialog::DownloadDialog(wxWindow *parent, const wxString &msg, const wxString &title, bool is_marked_msg /* = false*/, long style /* = wxOK | wxICON_INFORMATION*/) : MsgDialog(parent, title, msg, style), msg(msg) diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index c9b0ded3c5..a136d2eed1 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2022 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_MsgDialog_hpp_ #define slic3r_MsgDialog_hpp_ @@ -128,6 +132,8 @@ public: virtual ~WarningDialog() = default; }; +wxString get_wraped_wxString(const wxString& text_in, size_t line_len = 80); + #if 1 // Generic static line, used intead of wxStaticLine //class StaticLine: public wxTextCtrl diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 3f38e15bc3..12912d750d 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r/libslic3r.h" #include "OpenGLManager.hpp" @@ -65,6 +69,11 @@ const std::string& OpenGLManager::GLInfo::get_renderer() const return m_renderer; } +bool OpenGLManager::GLInfo::is_mesa() const +{ + return boost::icontains(m_version, "mesa"); +} + int OpenGLManager::GLInfo::get_max_tex_size() const { if (!m_detected) @@ -207,7 +216,7 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const OpenGLManager::GLInfo OpenGLManager::s_gl_info; bool OpenGLManager::s_compressed_textures_supported = false; -bool OpenGLManager::m_use_manually_generated_mipmaps = true; +bool OpenGLManager::s_force_power_of_two_textures = false; OpenGLManager::EMultisampleState OpenGLManager::s_multisample = OpenGLManager::EMultisampleState::Unknown; OpenGLManager::EFramebufferType OpenGLManager::s_framebuffers_type = OpenGLManager::EFramebufferType::Unknown; @@ -290,31 +299,22 @@ bool OpenGLManager::init_gl(bool popup_error) #ifdef _WIN32 // Since AMD driver version 22.7.1, there is probably some bug in the driver that causes the issue with the missing - // texture of the bed. It seems that this issue only triggers when mipmaps are generated manually - // (combined with a texture compression) and when mipmaps are generated through OpenGL glGenerateMipmap is working. - // So, for newer drivers than 22.6.1, the last working driver version, we use mipmaps generated through OpenGL. - if (const auto gl_info = OpenGLManager::get_gl_info(); boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.")) { - // WHQL drivers seem to have one more version number at the end besides non-WHQL drivers. - // WHQL: 4.6.14800 Compatibility Profile Context 22.6.1 30.0.21023.1015 - // Non-WHQL: 4.6.0 Compatibility Profile Context 22.8.1.220810 - std::regex version_rgx(R"(Compatibility\sProfile\sContext\s(\d+)\.(\d+)\.(\d+))"); - if (std::smatch matches; std::regex_search(gl_info.get_version(), matches, version_rgx) && matches.size() == 4) { - int version_major = std::stoi(matches[1].str()); - int version_minor = std::stoi(matches[2].str()); - int version_patch = std::stoi(matches[3].str()); - BOOST_LOG_TRIVIAL(debug) << "Found AMD driver version: " << version_major << "." << version_minor << "." << version_patch; - - if (version_major > 22 || (version_major == 22 && version_minor > 6) || (version_major == 22 && version_minor == 6 && version_patch > 1)) { - m_use_manually_generated_mipmaps = false; - BOOST_LOG_TRIVIAL(debug) << "Mipmapping through OpenGL was enabled."; - } - } else { - BOOST_LOG_TRIVIAL(error) << "Not recognized format of version."; - } - } else { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "not AMD driver."; - } -#endif + // texture of the bed (see: https://github.com/prusa3d/PrusaSlicer/issues/8417). + // It seems that this issue only triggers when mipmaps are generated manually + // (combined with a texture compression) with texture size not being power of two. + // When mipmaps are generated through OpenGL function glGenerateMipmap() the driver works fine, + // but the mipmap generation is quite slow on some machines. + // There is no an easy way to detect the driver version without using Win32 API because the strings returned by OpenGL + // have no standardized format, only some of them contain the driver version. + // Until we do not know that driver will be fixed (if ever) we force the use of power of two textures on all cards + // 1) containing the string 'Radeon' in the string returned by glGetString(GL_RENDERER) + // 2) containing the string 'Custom' in the string returned by glGetString(GL_RENDERER) + const auto& gl_info = OpenGLManager::get_gl_info(); + if (boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.") && + (boost::contains(gl_info.get_renderer(), "Radeon") || + boost::contains(gl_info.get_renderer(), "Custom"))) + s_force_power_of_two_textures = true; +#endif // _WIN32 } return true; diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index f2670f8af0..65a3dce6e8 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_OpenGLManager_hpp_ #define slic3r_OpenGLManager_hpp_ @@ -40,6 +44,8 @@ public: const std::string& get_vendor() const; const std::string& get_renderer() const; + bool is_mesa() const; + int get_max_tex_size() const; float get_max_anisotropy() const; @@ -81,10 +87,11 @@ private: static OSInfo s_os_info; #endif //__APPLE__ static bool s_compressed_textures_supported; + static bool s_force_power_of_two_textures; + static EMultisampleState s_multisample; static EFramebufferType s_framebuffers_type; - static bool m_use_manually_generated_mipmaps; public: OpenGLManager() = default; ~OpenGLManager(); @@ -101,7 +108,7 @@ public: static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } static wxGLCanvas* create_wxglcanvas(wxWindow& parent); static const GLInfo& get_gl_info() { return s_gl_info; } - static bool use_manually_generated_mipmaps() { return m_use_manually_generated_mipmaps; } + static bool force_power_of_two_textures() { return s_force_power_of_two_textures; } private: static void detect_multisample(int* attribList); diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index d07895b3de..edbb541a74 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -64,17 +64,17 @@ namespace GUI { class Bed3D; -std::array PartPlate::SELECT_COLOR = { 0.2666f, 0.2784f, 0.2784f, 1.0f }; //{ 0.4196f, 0.4235f, 0.4235f, 1.0f }; -std::array PartPlate::UNSELECT_COLOR = { 0.82f, 0.82f, 0.82f, 1.0f }; -std::array PartPlate::UNSELECT_DARK_COLOR = { 0.384f, 0.384f, 0.412f, 1.0f }; -std::array PartPlate::DEFAULT_COLOR = { 0.5f, 0.5f, 0.5f, 1.0f }; -std::array PartPlate::LINE_TOP_COLOR = { 0.89f, 0.89f, 0.89f, 1.0f }; -std::array PartPlate::LINE_TOP_DARK_COLOR = { 0.431f, 0.431f, 0.463f, 1.0f }; -std::array PartPlate::LINE_TOP_SEL_COLOR = { 0.5294f, 0.5451, 0.5333f, 1.0f}; -std::array PartPlate::LINE_TOP_SEL_DARK_COLOR = { 0.298f, 0.298f, 0.3333f, 1.0f}; -std::array PartPlate::LINE_BOTTOM_COLOR = { 0.8f, 0.8f, 0.8f, 0.4f }; -std::array PartPlate::HEIGHT_LIMIT_TOP_COLOR = { 0.6f, 0.6f, 1.0f, 1.0f }; -std::array PartPlate::HEIGHT_LIMIT_BOTTOM_COLOR = { 0.4f, 0.4f, 1.0f, 1.0f }; +ColorRGBA PartPlate::SELECT_COLOR = { 0.2666f, 0.2784f, 0.2784f, 1.0f }; //{ 0.4196f, 0.4235f, 0.4235f, 1.0f }; +ColorRGBA PartPlate::UNSELECT_COLOR = { 0.82f, 0.82f, 0.82f, 1.0f }; +ColorRGBA PartPlate::UNSELECT_DARK_COLOR = { 0.384f, 0.384f, 0.412f, 1.0f }; +ColorRGBA PartPlate::DEFAULT_COLOR = { 0.5f, 0.5f, 0.5f, 1.0f }; +ColorRGBA PartPlate::LINE_TOP_COLOR = { 0.89f, 0.89f, 0.89f, 1.0f }; +ColorRGBA PartPlate::LINE_TOP_DARK_COLOR = { 0.431f, 0.431f, 0.463f, 1.0f }; +ColorRGBA PartPlate::LINE_TOP_SEL_COLOR = { 0.5294f, 0.5451, 0.5333f, 1.0f}; +ColorRGBA PartPlate::LINE_TOP_SEL_DARK_COLOR = { 0.298f, 0.298f, 0.3333f, 1.0f}; +ColorRGBA PartPlate::LINE_BOTTOM_COLOR = { 0.8f, 0.8f, 0.8f, 0.4f }; +ColorRGBA PartPlate::HEIGHT_LIMIT_TOP_COLOR = { 0.6f, 0.6f, 1.0f, 1.0f }; +ColorRGBA PartPlate::HEIGHT_LIMIT_BOTTOM_COLOR = { 0.4f, 0.4f, 1.0f, 1.0f }; // get text extent with wxMemoryDC void get_text_extent(const wxString &msg, wxCoord &w, wxCoord &h, wxFont *font) @@ -116,20 +116,20 @@ wxFont* find_font(const std::string& text_str, int max_size = 32) } void PartPlate::update_render_colors() { - PartPlate::SELECT_COLOR = GLColor(RenderColor::colors[RenderCol_Plate_Selected]); - PartPlate::UNSELECT_COLOR = GLColor(RenderColor::colors[RenderCol_Plate_Unselected]); - PartPlate::DEFAULT_COLOR = GLColor(RenderColor::colors[RenderCol_Plate_Default]); - PartPlate::LINE_TOP_COLOR = GLColor(RenderColor::colors[RenderCol_Plate_Line_Top]); - PartPlate::LINE_BOTTOM_COLOR = GLColor(RenderColor::colors[RenderCol_Plate_Line_Bottom]); + PartPlate::SELECT_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Plate_Selected]); + PartPlate::UNSELECT_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Plate_Unselected]); + PartPlate::DEFAULT_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Plate_Default]); + PartPlate::LINE_TOP_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Plate_Line_Top]); + PartPlate::LINE_BOTTOM_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Plate_Line_Bottom]); } void PartPlate::load_render_colors() { - RenderColor::colors[RenderCol_Plate_Selected] = IMColor(SELECT_COLOR); - RenderColor::colors[RenderCol_Plate_Unselected] = IMColor(UNSELECT_COLOR); - RenderColor::colors[RenderCol_Plate_Default] = IMColor(DEFAULT_COLOR); - RenderColor::colors[RenderCol_Plate_Line_Top] = IMColor(LINE_TOP_COLOR); - RenderColor::colors[RenderCol_Plate_Line_Bottom] = IMColor(LINE_BOTTOM_COLOR); + RenderColor::colors[RenderCol_Plate_Selected] = ImGuiWrapper::to_ImVec4(SELECT_COLOR); + RenderColor::colors[RenderCol_Plate_Unselected] = ImGuiWrapper::to_ImVec4(UNSELECT_COLOR); + RenderColor::colors[RenderCol_Plate_Default] = ImGuiWrapper::to_ImVec4(DEFAULT_COLOR); + RenderColor::colors[RenderCol_Plate_Line_Top] = ImGuiWrapper::to_ImVec4(LINE_TOP_COLOR); + RenderColor::colors[RenderCol_Plate_Line_Bottom] = ImGuiWrapper::to_ImVec4(LINE_BOTTOM_COLOR); } @@ -151,7 +151,6 @@ PartPlate::~PartPlate() clear(); //if (m_quadric != nullptr) // ::gluDeleteQuadric(m_quadric); - release_opengl_resource(); //boost::nowide::remove(m_tmp_gcode_path.c_str()); } @@ -170,7 +169,6 @@ void PartPlate::init() m_print_index = -1; m_print = nullptr; - m_plate_name_vbo_id = 0; } BedType PartPlate::get_bed_type(bool load_from_project) const @@ -333,17 +331,86 @@ void PartPlate::calc_bounding_boxes() const { } } -void PartPlate::calc_triangles(const ExPolygon& poly) { - if (!m_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) +void PartPlate::calc_triangles(const ExPolygon &poly) +{ + m_triangles.reset(); + + if (!init_model_from_poly(m_triangles.model, poly, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create plate triangles\n"; } -void PartPlate::calc_exclude_triangles(const ExPolygon& poly) { - if (!m_exclude_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) +void PartPlate::calc_exclude_triangles(const ExPolygon &poly) +{ + m_exclude_triangles.reset(); + + if (!init_model_from_poly(m_exclude_triangles, poly, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create exclude triangles\n"; } +static bool init_model_from_lines(GLModel &model, const Lines &lines, float z) +{ + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * lines.size()); + init_data.reserve_indices(2 * lines.size()); + + for (const auto &l : lines) { + init_data.add_vertex(Vec3f(unscale(l.a.x()), unscale(l.a.y()), z)); + init_data.add_vertex(Vec3f(unscale(l.b.x()), unscale(l.b.y()), z)); + const unsigned int vertices_counter = (unsigned int)init_data.vertices_count(); + init_data.add_line(vertices_counter - 2, vertices_counter - 1); + } + + model.init_from(std::move(init_data)); + + return true; +} + +static bool init_model_from_lines(GLModel &model, const Lines3 &lines) +{ + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * lines.size()); + init_data.reserve_indices(2 * lines.size()); + + for (const auto &l : lines) { + init_data.add_vertex(Vec3f(unscale(l.a.x()), unscale(l.a.y()), unscale(l.a.z()))); + init_data.add_vertex(Vec3f(unscale(l.b.x()), unscale(l.b.y()), unscale(l.b.z()))); + const unsigned int vertices_counter = (unsigned int) init_data.vertices_count(); + init_data.add_line(vertices_counter - 2, vertices_counter - 1); + } + + model.init_from(std::move(init_data)); + + return true; +} + +static void init_raycaster_from_model(PickingModel& model) +{ + assert(model.mesh_raycaster == nullptr); + + const GLModel::Geometry &geometry = model.model.get_geometry(); + + indexed_triangle_set its; + its.vertices.reserve(geometry.vertices_count()); + for (size_t i = 0; i < geometry.vertices_count(); ++i) { + its.vertices.emplace_back(geometry.extract_position_3(i)); + } + its.indices.reserve(geometry.indices_count() / 3); + for (size_t i = 0; i < geometry.indices_count() / 3; ++i) { + const size_t tri_id = i * 3; + its.indices.emplace_back(geometry.extract_index(tri_id), geometry.extract_index(tri_id + 1), geometry.extract_index(tri_id + 2)); + } + + model.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); +} + void PartPlate::calc_gridlines(const ExPolygon& poly, const BoundingBox& pp_bbox) { + m_gridlines.reset(); + m_gridlines_bolder.reset(); + Polylines axes_lines, axes_lines_bolder; int count = 0; for (coord_t x = pp_bbox.min(0); x <= pp_bbox.max(0); x += scale_(10.0)) { @@ -379,14 +446,18 @@ void PartPlate::calc_gridlines(const ExPolygon& poly, const BoundingBox& pp_bbox Lines contour_lines = to_lines(poly); std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines)); - if (!m_gridlines.set_from_lines(gridlines, GROUND_Z_GRIDLINE)) + if (!init_model_from_lines(m_gridlines, gridlines, GROUND_Z_GRIDLINE)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create bed grid lines\n"; - if (!m_gridlines_bolder.set_from_lines(gridlines_bolder, GROUND_Z_GRIDLINE)) + if (!init_model_from_lines(m_gridlines_bolder, gridlines_bolder, GROUND_Z_GRIDLINE)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create bed grid lines\n"; } void PartPlate::calc_height_limit() { + m_height_limit_common.reset(); + m_height_limit_bottom.reset(); + m_height_limit_top.reset(); + Lines3 bottom_h_lines, top_lines, top_h_lines, common_lines; int shape_count = m_shape.size(); float first_z = 0.02f; @@ -418,18 +489,20 @@ void PartPlate::calc_height_limit() { //std::copy(bottom_lines.begin(), bottom_lines.end(), std::back_inserter(bottom_h_lines)); std::copy(top_lines.begin(), top_lines.end(), std::back_inserter(top_h_lines)); - if (!m_height_limit_common.set_from_3d_Lines(common_lines)) + if (!init_model_from_lines(m_height_limit_common, common_lines)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create height limit bottom lines\n"; - if (!m_height_limit_bottom.set_from_3d_Lines(bottom_h_lines)) + if (!init_model_from_lines(m_height_limit_bottom, bottom_h_lines)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create height limit bottom lines\n"; - if (!m_height_limit_top.set_from_3d_Lines(top_h_lines)) + if (!init_model_from_lines(m_height_limit_top, top_h_lines)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create height limit top lines\n"; } -void PartPlate::calc_vertex_for_number(int index, bool one_number, GeometryBuffer &buffer) +void PartPlate::calc_vertex_for_number(int index, bool one_number, GLModel &buffer) { + buffer.reset(); + ExPolygon poly; #if 0 //in the up area Vec2d& p = m_shape[2]; @@ -449,13 +522,14 @@ void PartPlate::calc_vertex_for_number(int index, bool one_number, GeometryBuffe poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) + PARTPLATE_ICON_SIZE - PARTPLATE_TEXT_OFFSET_Y)}); poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) + PARTPLATE_ICON_SIZE - PARTPLATE_TEXT_OFFSET_Y) }); #endif - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) + if (!init_model_from_poly(buffer, poly, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; } -void PartPlate::calc_vertex_for_icons(int index, GeometryBuffer &buffer) +void PartPlate::calc_vertex_for_icons(int index, PickingModel &model) { + model.reset(); + ExPolygon poly; auto bed_ext = get_extents(m_shape); Vec2d p = bed_ext[2]; @@ -468,13 +542,17 @@ void PartPlate::calc_vertex_for_icons(int index, GeometryBuffer &buffer) poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y)- PARTPLATE_ICON_GAP_TOP)}); poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y)- PARTPLATE_ICON_GAP_TOP) }); - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) + if (!init_model_from_poly(model.model, poly, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; + + init_raycaster_from_model(model); } -void PartPlate::calc_vertex_for_icons_background(int icon_count, GeometryBuffer &buffer) +/* +void PartPlate::calc_vertex_for_icons_background(int icon_count, GLModel &buffer) { + buffer.reset(); + ExPolygon poly; auto bed_ext = get_extents(m_shape); Vec2d p = bed_ext[2]; @@ -484,38 +562,37 @@ void PartPlate::calc_vertex_for_icons_background(int icon_count, GeometryBuffer poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE), scale_(p(1) - PARTPLATE_ICON_GAP_TOP)}); poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT), scale_(p(1) - PARTPLATE_ICON_GAP_TOP) }); - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) + if (!init_model_from_poly(buffer, poly, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; } +*/ -void PartPlate::render_background(bool force_default_color) const { - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - +void PartPlate::render_background(bool force_default_color) +{ //return directly for current plate if (m_selected && !force_default_color) return; // draw background glsafe(::glDepthMask(GL_FALSE)); + ColorRGBA color; if (!force_default_color) { if (m_selected) { - glsafe(::glColor4fv(PartPlate::SELECT_COLOR.data())); + color = PartPlate::SELECT_COLOR; } else { - glsafe(m_partplate_list->m_is_dark ? ::glColor4fv(PartPlate::UNSELECT_DARK_COLOR.data()) : ::glColor4fv(PartPlate::UNSELECT_COLOR.data())); + color = m_partplate_list->m_is_dark ? PartPlate::UNSELECT_DARK_COLOR : PartPlate::UNSELECT_COLOR; } } else { - glsafe(::glColor4fv(PartPlate::DEFAULT_COLOR.data())); + color = PartPlate::DEFAULT_COLOR; } - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + m_triangles.model.set_color(color); + m_triangles.model.render(); glsafe(::glDepthMask(GL_TRUE)); } -void PartPlate::render_logo_texture(GLTexture &logo_texture, const GeometryBuffer& logo_buffer, bool bottom, unsigned int vbo_id) const +void PartPlate::render_logo_texture(GLTexture &logo_texture, GLModel& logo_buffer, bool bottom) { //check valid if (logo_texture.unsent_compressed_data_available()) { @@ -523,53 +600,37 @@ void PartPlate::render_logo_texture(GLTexture &logo_texture, const GeometryBuffe logo_texture.send_compressed_data_to_gpu(); } - if (logo_buffer.get_vertices_count() > 0) { + if (logo_buffer.is_initialized()) { GLShaderProgram* shader = wxGetApp().get_shader("printbed"); if (shader != nullptr) { shader->start_using(); + const Camera &camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("transparent_background", 0); shader->set_uniform("svg_source", 0); //glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDepthMask(GL_FALSE)); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + if (bottom) glsafe(::glFrontFace(GL_CW)); - unsigned int stride = logo_buffer.get_vertex_data_size(); - - GLint position_id = shader->get_attrib_location("v_position"); - GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); - if (position_id != -1) { - glsafe(::glEnableVertexAttribArray(position_id)); - } - if (tex_coords_id != -1) { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - } - // show the temporary texture while no compressed data is available GLuint tex_id = (GLuint)logo_texture.get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id)); - - if (position_id != -1) - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)logo_buffer.get_position_offset())); - if (tex_coords_id != -1) - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)logo_buffer.get_tex_coords_offset())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)logo_buffer.get_vertices_count())); - - if (tex_coords_id != -1) - glsafe(::glDisableVertexAttribArray(tex_coords_id)); - - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + logo_buffer.render(); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); if (bottom) glsafe(::glFrontFace(GL_CCW)); + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDepthMask(GL_TRUE)); shader->stop_using(); @@ -577,7 +638,7 @@ void PartPlate::render_logo_texture(GLTexture &logo_texture, const GeometryBuffe } } -void PartPlate::render_logo(bool bottom, bool render_cali) const +void PartPlate::render_logo(bool bottom, bool render_cali) { if (!m_partplate_list->render_bedtype_logo) { // render third-party printer texture logo @@ -643,15 +704,8 @@ void PartPlate::render_logo(bool bottom, bool render_cali) const //canvas.request_extra_frame(); } - if (m_vbo_id == 0) { - unsigned int* vbo_id_ptr = const_cast(&m_vbo_id); - glsafe(::glGenBuffers(1, vbo_id_ptr)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id_ptr)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_logo_triangles.get_vertices_data_size(), (const GLvoid*)m_logo_triangles.get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - if (m_vbo_id != 0 && m_logo_triangles.get_vertices_count() > 0) - render_logo_texture(m_partplate_list->m_logo_texture, m_logo_triangles, bottom, m_vbo_id); + if (m_logo_triangles.is_initialized()) + render_logo_texture(m_partplate_list->m_logo_texture, m_logo_triangles, bottom); return; } @@ -669,7 +723,7 @@ void PartPlate::render_logo(bool bottom, bool render_cali) const // render bed textures for (auto &part : m_partplate_list->bed_texture_info[bed_type_idx].parts) { if (part.texture) { - if (part.buffer && part.buffer->get_vertices_count() > 0 + if (part.buffer && part.buffer->is_initialized() //&& part.vbo_id != 0 ) { if (part.offset.x() != m_origin.x() || part.offset.y() != m_origin.y()) { @@ -678,8 +732,7 @@ void PartPlate::render_logo(bool bottom, bool render_cali) const } render_logo_texture(*(part.texture), *(part.buffer), - bottom, - part.vbo_id); + bottom); } } } @@ -688,29 +741,27 @@ void PartPlate::render_logo(bool bottom, bool render_cali) const if (render_cali) { for (auto& part : m_partplate_list->cali_texture_info.parts) { if (part.texture) { - if (part.buffer && part.buffer->get_vertices_count() > 0) { + if (part.buffer && part.buffer->is_initialized()) { if (part.offset.x() != m_origin.x() || part.offset.y() != m_origin.y()) { part.offset = Vec2d(m_origin.x(), m_origin.y()); part.update_buffer(); } render_logo_texture(*(part.texture), *(part.buffer), - bottom, - part.vbo_id); + bottom); } } } } } -void PartPlate::render_exclude_area(bool force_default_color) const { +void PartPlate::render_exclude_area(bool force_default_color) { if (force_default_color) //for thumbnail case return; - unsigned int triangles_vcount = m_exclude_triangles.get_vertices_count(); - std::array select_color{ 0.765f, 0.7686f, 0.7686f, 1.0f }; - std::array unselect_color{ 0.9f, 0.9f, 0.9f, 1.0f }; - std::array default_color{ 0.9f, 0.9f, 0.9f, 1.0f }; + ColorRGBA select_color{ 0.765f, 0.7686f, 0.7686f, 1.0f }; + ColorRGBA unselect_color{ 0.9f, 0.9f, 0.9f, 1.0f }; + //ColorRGBA default_color{ 0.9f, 0.9f, 0.9f, 1.0f }; // draw exclude area glsafe(::glDepthMask(GL_FALSE)); @@ -722,14 +773,13 @@ void PartPlate::render_exclude_area(bool force_default_color) const { glsafe(::glColor4fv(unselect_color.data())); } - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_exclude_triangles.get_vertex_data_size(), (GLvoid*)m_exclude_triangles.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + m_exclude_triangles.set_color(m_selected ? select_color : unselect_color); + m_exclude_triangles.render(); glsafe(::glDepthMask(GL_TRUE)); } -/*void PartPlate::render_background_for_picking(const float* render_color) const +/*void PartPlate::render_background_for_picking(const ColorRGBA render_color) const { unsigned int triangles_vcount = m_triangles.get_vertices_count(); @@ -741,99 +791,68 @@ void PartPlate::render_exclude_area(bool force_default_color) const { glsafe(::glDepthMask(GL_TRUE)); }*/ -void PartPlate::render_grid(bool bottom) const { +void PartPlate::render_grid(bool bottom) { //glsafe(::glEnable(GL_MULTISAMPLE)); // draw grid glsafe(::glLineWidth(1.0f * m_scale_factor)); + + ColorRGBA color; if (bottom) - glsafe(::glColor4fv(LINE_BOTTOM_COLOR.data())); + color = LINE_BOTTOM_COLOR; else { if (m_selected) - glsafe(m_partplate_list->m_is_dark ? ::glColor4fv(LINE_TOP_SEL_DARK_COLOR.data()) : ::glColor4fv(LINE_TOP_SEL_COLOR.data())); + color = m_partplate_list->m_is_dark ? LINE_TOP_SEL_DARK_COLOR : LINE_TOP_SEL_COLOR; else - glsafe(m_partplate_list->m_is_dark ? ::glColor4fv(LINE_TOP_DARK_COLOR.data()) : ::glColor4fv(LINE_TOP_COLOR.data())); + color = m_partplate_list->m_is_dark ? LINE_TOP_DARK_COLOR : LINE_TOP_COLOR; } - glsafe(::glVertexPointer(3, GL_FLOAT, m_gridlines.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); + m_gridlines.set_color(color); + m_gridlines.render(); glsafe(::glLineWidth(2.0f * m_scale_factor)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_gridlines_bolder.get_vertex_data_size(), (GLvoid*)m_gridlines_bolder.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines_bolder.get_vertices_count())); + m_gridlines_bolder.set_color(color); + m_gridlines_bolder.render(); } -void PartPlate::render_height_limit(PartPlate::HeightLimitMode mode) const +void PartPlate::render_height_limit(PartPlate::HeightLimitMode mode) { if (m_print && m_print->config().print_sequence == PrintSequence::ByObject && mode != HEIGHT_LIMIT_NONE) { // draw lower limit glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4fv(HEIGHT_LIMIT_BOTTOM_COLOR.data())); - glsafe(::glVertexPointer(3, GL_FLOAT, m_height_limit_common.get_vertex_data_size(), (GLvoid*)m_height_limit_common.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_height_limit_common.get_vertices_count())); + m_height_limit_common.set_color(HEIGHT_LIMIT_BOTTOM_COLOR); + m_height_limit_common.render(); if ((mode == HEIGHT_LIMIT_BOTTOM) || (mode == HEIGHT_LIMIT_BOTH)) { glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4fv(HEIGHT_LIMIT_BOTTOM_COLOR.data())); - glsafe(::glVertexPointer(3, GL_FLOAT, m_height_limit_bottom.get_vertex_data_size(), (GLvoid*)m_height_limit_bottom.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_height_limit_bottom.get_vertices_count())); + m_height_limit_bottom.set_color(HEIGHT_LIMIT_BOTTOM_COLOR); + m_height_limit_bottom.render(); } // draw upper limit if ((mode == HEIGHT_LIMIT_TOP) || (mode == HEIGHT_LIMIT_BOTH)){ - glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4fv(HEIGHT_LIMIT_TOP_COLOR.data())); - glsafe(::glVertexPointer(3, GL_FLOAT, m_height_limit_top.get_vertex_data_size(), (GLvoid*)m_height_limit_top.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_height_limit_top.get_vertices_count())); + glsafe(::glLineWidth(3.0f * m_scale_factor)); + m_height_limit_top.set_color(HEIGHT_LIMIT_TOP_COLOR); + m_height_limit_top.render(); } } } - -void PartPlate::render_icon_texture(int position_id, int tex_coords_id, const GeometryBuffer &buffer, GLTexture &texture, unsigned int &vbo_id) const +void PartPlate::render_icon_texture(GLModel &buffer, GLTexture &texture) { - if (vbo_id == 0) { - glsafe(::glGenBuffers(1, &vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)buffer.get_vertices_data_size(), (const GLvoid*)buffer.get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - unsigned int stride = buffer.get_vertex_data_size(); GLuint tex_id = (GLuint)texture.get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id)); - if (position_id != -1) - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)buffer.get_position_offset())); - if (tex_coords_id != -1) - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)buffer.get_tex_coords_offset())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)buffer.get_vertices_count())); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + buffer.render(); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); } -void PartPlate::render_plate_name_texture(int position_id, int tex_coords_id) + +void PartPlate::render_plate_name_texture() { if (m_name_texture.get_id() == 0) generate_plate_name_texture(); - if (m_plate_name_vbo_id == 0 && m_plate_name_icon.get_vertices_data_size() > 0) { - glsafe(::glGenBuffers(1, &m_plate_name_vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_plate_name_vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_plate_name_icon.get_vertices_data_size(), (const GLvoid*)m_plate_name_icon.get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - unsigned int stride = m_plate_name_icon.get_vertex_data_size(); GLuint tex_id = (GLuint)m_name_texture.get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_plate_name_vbo_id)); - if (position_id != -1) - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_plate_name_icon.get_position_offset())); - if (tex_coords_id != -1) - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_plate_name_icon.get_tex_coords_offset())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_plate_name_icon.get_vertices_count())); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + m_plate_name_icon.render(); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); } @@ -857,6 +876,9 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) GLShaderProgram* shader = wxGetApp().get_shader("printbed"); if (shader != nullptr) { shader->start_using(); + const Camera &camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("transparent_background", bottom); //shader->set_uniform("svg_source", boost::algorithm::iends_with(m_partplate_list->m_del_texture.get_source(), ".svg")); shader->set_uniform("svg_source", 0); @@ -865,92 +887,72 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) // glsafe(::glFrontFace(GL_CW)); glsafe(::glDepthMask(GL_FALSE)); - GLint position_id = shader->get_attrib_location("v_position"); - GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); - if (position_id != -1) { - glsafe(::glEnableVertexAttribArray(position_id)); - } - if (tex_coords_id != -1) { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - } + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + if (!only_name) { if (hover_id == 1) { - render_icon_texture(position_id, tex_coords_id, m_del_icon, m_partplate_list->m_del_hovered_texture, - m_del_vbo_id); + render_icon_texture(m_del_icon.model, m_partplate_list->m_del_hovered_texture); show_tooltip(_u8L("Remove current plate (if not last one)")); } else - render_icon_texture(position_id, tex_coords_id, m_del_icon, m_partplate_list->m_del_texture, - m_del_vbo_id); + render_icon_texture(m_del_icon.model, m_partplate_list->m_del_texture); if (hover_id == 2) { - render_icon_texture(position_id, tex_coords_id, m_orient_icon, - m_partplate_list->m_orient_hovered_texture, m_orient_vbo_id); + render_icon_texture(m_orient_icon.model, m_partplate_list->m_orient_hovered_texture); show_tooltip(_u8L("Auto orient objects on current plate")); } else - render_icon_texture(position_id, tex_coords_id, m_orient_icon, m_partplate_list->m_orient_texture, - m_orient_vbo_id); + render_icon_texture(m_orient_icon.model, m_partplate_list->m_orient_texture); if (hover_id == 3) { - render_icon_texture(position_id, tex_coords_id, m_arrange_icon, - m_partplate_list->m_arrange_hovered_texture, m_arrange_vbo_id); + render_icon_texture(m_arrange_icon.model, m_partplate_list->m_arrange_hovered_texture); show_tooltip(_u8L("Arrange objects on current plate")); } else - render_icon_texture(position_id, tex_coords_id, m_arrange_icon, m_partplate_list->m_arrange_texture, - m_arrange_vbo_id); + render_icon_texture(m_arrange_icon.model, m_partplate_list->m_arrange_texture); if (hover_id == 4) { if (this->is_locked()) { - render_icon_texture(position_id, tex_coords_id, m_lock_icon, - m_partplate_list->m_locked_hovered_texture, m_lock_vbo_id); + render_icon_texture(m_lock_icon.model, + m_partplate_list->m_locked_hovered_texture); show_tooltip(_u8L("Unlock current plate")); } else { - render_icon_texture(position_id, tex_coords_id, m_lock_icon, - m_partplate_list->m_lockopen_hovered_texture, m_lock_vbo_id); + render_icon_texture(m_lock_icon.model, + m_partplate_list->m_lockopen_hovered_texture); show_tooltip(_u8L("Lock current plate")); } } else { if (this->is_locked()) - render_icon_texture(position_id, tex_coords_id, m_lock_icon, m_partplate_list->m_locked_texture, - m_lock_vbo_id); + render_icon_texture(m_lock_icon.model, m_partplate_list->m_locked_texture); else - render_icon_texture(position_id, tex_coords_id, m_lock_icon, m_partplate_list->m_lockopen_texture, - m_lock_vbo_id); + render_icon_texture(m_lock_icon.model, m_partplate_list->m_lockopen_texture); } if (m_partplate_list->render_plate_settings) { if (hover_id == 5) { if (get_bed_type() == BedType::btDefault && get_print_seq() == PrintSequence::ByDefault && get_first_layer_print_sequence().empty()) - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_hovered_texture, m_plate_settings_vbo_id); + render_icon_texture(m_plate_settings_icon.model, m_partplate_list->m_plate_settings_hovered_texture); else - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, - m_partplate_list->m_plate_settings_changed_hovered_texture, - m_plate_settings_vbo_id); + render_icon_texture(m_plate_settings_icon.model, m_partplate_list->m_plate_settings_changed_hovered_texture); show_tooltip(_u8L("Customize current plate")); } else { if (get_bed_type() == BedType::btDefault && get_print_seq() == PrintSequence::ByDefault && get_first_layer_print_sequence().empty()) - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_texture, m_plate_settings_vbo_id); + render_icon_texture(m_plate_settings_icon.model, m_partplate_list->m_plate_settings_texture); else - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, - m_partplate_list->m_plate_settings_changed_texture, m_plate_settings_vbo_id); + render_icon_texture(m_plate_settings_icon.model, m_partplate_list->m_plate_settings_changed_texture); } } if (m_plate_index >= 0 && m_plate_index < MAX_PLATE_COUNT) { - render_icon_texture(position_id, tex_coords_id, m_plate_idx_icon, - m_partplate_list->m_idx_textures[m_plate_index], m_plate_idx_vbo_id); + render_icon_texture(m_plate_idx_icon, m_partplate_list->m_idx_textures[m_plate_index]); } } - render_plate_name_texture(position_id, tex_coords_id); - if (tex_coords_id != -1) - glsafe(::glDisableVertexAttribArray(tex_coords_id)); + render_plate_name_texture(); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); + glsafe(::glDisable(GL_BLEND)); //if (bottom) // glsafe(::glFrontFace(GL_CCW)); @@ -960,11 +962,14 @@ void PartPlate::render_icons(bool bottom, bool only_name, int hover_id) } } -void PartPlate::render_only_numbers(bool bottom) const +void PartPlate::render_only_numbers(bool bottom) { GLShaderProgram* shader = wxGetApp().get_shader("printbed"); if (shader != nullptr) { shader->start_using(); + const Camera &camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("transparent_background", bottom); //shader->set_uniform("svg_source", boost::algorithm::iends_with(m_partplate_list->m_del_texture.get_source(), ".svg")); shader->set_uniform("svg_source", 0); @@ -973,24 +978,14 @@ void PartPlate::render_only_numbers(bool bottom) const // glsafe(::glFrontFace(GL_CW)); glsafe(::glDepthMask(GL_FALSE)); - GLint position_id = shader->get_attrib_location("v_position"); - GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); - if (position_id != -1) { - glsafe(::glEnableVertexAttribArray(position_id)); - } - if (tex_coords_id != -1) { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - } + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); if (m_plate_index >=0 && m_plate_index < MAX_PLATE_COUNT) { - render_icon_texture(position_id, tex_coords_id, m_plate_idx_icon, m_partplate_list->m_idx_textures[m_plate_index], m_plate_idx_vbo_id); + render_icon_texture(m_plate_idx_icon, m_partplate_list->m_idx_textures[m_plate_index]); } - if (tex_coords_id != -1) - glsafe(::glDisableVertexAttribArray(tex_coords_id)); - - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); + glsafe(::glDisable(GL_BLEND)); //if (bottom) // glsafe(::glFrontFace(GL_CCW)); @@ -1000,20 +995,7 @@ void PartPlate::render_only_numbers(bool bottom) const } } -void PartPlate::render_rectangle_for_picking(const GeometryBuffer &buffer, const float* render_color) const -{ - unsigned int triangles_vcount = buffer.get_vertices_count(); - - //glsafe(::glDepthMask(GL_FALSE)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glColor4fv(render_color)); - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, buffer.get_vertex_data_size(), (GLvoid*)buffer.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - //glsafe(::glDepthMask(GL_TRUE)); -} - +/* void PartPlate::render_label(GLCanvas3D& canvas) const { std::string label = (boost::format("Plate %1%") % (m_plate_index + 1)).str(); const Camera& camera = wxGetApp().plater()->get_camera(); @@ -1058,14 +1040,14 @@ void PartPlate::render_label(GLCanvas3D& canvas) const { } -void PartPlate::render_grabber(const float* render_color, bool use_lighting) const +void PartPlate::render_grabber(const ColorRGBA render_color, bool use_lighting) const { BoundingBoxf3* bounding_box = const_cast(&m_bounding_box); const Vec3d& center = m_grabber_box.center(); if (use_lighting) glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glColor4fv(render_color)); + glsafe(::glColor4fv(render_color.data())); glsafe(::glPushMatrix()); glsafe(::glTranslated(center(0), center(1), center(2))); @@ -1138,7 +1120,7 @@ void PartPlate::render_face(float x_size, float y_size) const glsafe(::glEnd()); } -void PartPlate::render_arrows(const float* render_color, bool use_lighting) const +void PartPlate::render_arrows(const ColorRGBA render_color, bool use_lighting) const { #if 0 if (m_quadric == nullptr) @@ -1176,7 +1158,7 @@ void PartPlate::render_arrows(const float* render_color, bool use_lighting) cons #endif } -void PartPlate::render_left_arrow(const float* render_color, bool use_lighting) const +void PartPlate::render_left_arrow(const ColorRGBA render_color, bool use_lighting) const { #if 0 if (m_quadric == nullptr) @@ -1203,7 +1185,7 @@ void PartPlate::render_left_arrow(const float* render_color, bool use_lighting) glsafe(::glDisable(GL_LIGHTING)); #endif } -void PartPlate::render_right_arrow(const float* render_color, bool use_lighting) const +void PartPlate::render_right_arrow(const ColorRGBA render_color, bool use_lighting) const { #if 0 if (m_quadric == nullptr) @@ -1229,103 +1211,28 @@ void PartPlate::render_right_arrow(const float* render_color, bool use_lighting) glsafe(::glDisable(GL_LIGHTING)); #endif } +*/ -void PartPlate::on_render_for_picking() const { - //glsafe(::glDisable(GL_DEPTH_TEST)); - int hover_id = 0; - std::array color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - //render_grabber(m_grabber_color, false); - render_rectangle_for_picking(m_triangles, m_grabber_color); - hover_id = 1; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - //render_left_arrow(m_grabber_color, false); - render_rectangle_for_picking(m_del_icon, m_grabber_color); - hover_id = 2; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - render_rectangle_for_picking(m_orient_icon, m_grabber_color); - hover_id = 3; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - render_rectangle_for_picking(m_arrange_icon, m_grabber_color); - hover_id = 4; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - //render_right_arrow(m_grabber_color, false); - render_rectangle_for_picking(m_lock_icon, m_grabber_color); - hover_id = 5; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; +static void register_model_for_picking(GLCanvas3D &canvas, PickingModel &model, int id) +{ + canvas.add_raycaster_for_picking(SceneRaycaster::EType::Bed, id, *model.mesh_raycaster, Transform3d::Identity()); +} + +void PartPlate::register_raycasters_for_picking(GLCanvas3D &canvas) +{ + register_model_for_picking(canvas, m_triangles, picking_id_component(0)); + register_model_for_picking(canvas, m_del_icon, picking_id_component(1)); + register_model_for_picking(canvas, m_orient_icon, picking_id_component(2)); + register_model_for_picking(canvas, m_arrange_icon, picking_id_component(3)); + register_model_for_picking(canvas, m_lock_icon, picking_id_component(4)); if (m_partplate_list->render_plate_settings) - render_rectangle_for_picking(m_plate_settings_icon, m_grabber_color); + register_model_for_picking(canvas, m_plate_settings_icon, picking_id_component(5)); } -std::array PartPlate::picking_color_component(int idx) const +int PartPlate::picking_id_component(int idx) const { - static const float INV_255 = 1.0f / 255.0f; unsigned int id = PLATE_BASE_ID - this->m_plate_index * GRABBER_COUNT - idx; - return std::array { - float((id >> 0) & 0xff)* INV_255, // red - float((id >> 8) & 0xff)* INV_255, // greeen - float((id >> 16) & 0xff)* INV_255, // blue - float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 - }; -} - -void PartPlate::release_opengl_resource() -{ - if (m_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_vbo_id)); - m_vbo_id = 0; - } - if (m_del_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_del_vbo_id)); - m_del_vbo_id = 0; - } - if (m_orient_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_orient_vbo_id)); - m_orient_vbo_id = 0; - } - if (m_arrange_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_arrange_vbo_id)); - m_arrange_vbo_id = 0; - } - if (m_lock_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_lock_vbo_id)); - m_lock_vbo_id = 0; - } - if (m_plate_settings_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_settings_vbo_id)); - m_plate_settings_vbo_id = 0; - } - if (m_plate_idx_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_idx_vbo_id)); - m_plate_idx_vbo_id = 0; - } - if (m_plate_name_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_name_vbo_id)); - m_plate_name_vbo_id = 0; - } + return id; } std::vector PartPlate::get_extruders(bool conside_custom_gcode) const @@ -1764,6 +1671,8 @@ Vec3d PartPlate::get_center_origin() void PartPlate::generate_plate_name_texture() { + m_plate_name_icon.reset(); + // generate m_name_texture texture from m_name with generate_from_text_string m_name_texture.reset(); auto text = m_name.empty()? _L("Untitled") : from_u8(m_name); @@ -1786,14 +1695,8 @@ void PartPlate::generate_plate_name_texture() poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + w - offset_x), scale_(p(1) - PARTPLATE_TEXT_OFFSET_Y)}); poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) - PARTPLATE_TEXT_OFFSET_Y) }); - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!m_plate_name_icon.set_from_triangles(triangles, GROUND_Z)) + if (!init_model_from_poly(m_plate_name_icon, poly, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; - - if (m_plate_name_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_name_vbo_id)); - m_plate_name_vbo_id = 0; - } } void PartPlate::set_plate_name(const std::string& name) { @@ -2499,11 +2402,9 @@ bool PartPlate::set_shape(const Pointfs& shape, const Pointfs& exclude_areas, Ve ExPolygon logo_poly; generate_logo_polygon(logo_poly); - if (!m_logo_triangles.set_from_triangles(triangulate_expolygon_2f(logo_poly, NORMALS_UP), GROUND_Z+0.02f)) + m_logo_triangles.reset(); + if (!init_model_from_poly(m_logo_triangles, logo_poly, GROUND_Z + 0.02f)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create logo triangles\n"; - else { - ; - } ExPolygon poly; /*for (const Vec2d& p : m_shape) { @@ -2511,6 +2412,7 @@ bool PartPlate::set_shape(const Pointfs& shape, const Pointfs& exclude_areas, Ve }*/ generate_print_polygon(poly); calc_triangles(poly); + init_raycaster_from_model(m_triangles); ExPolygon exclude_poly; /*for (const Vec2d& p : m_exclude_area) { @@ -2525,10 +2427,10 @@ bool PartPlate::set_shape(const Pointfs& shape, const Pointfs& exclude_areas, Ve //calc_vertex_for_icons_background(5, m_del_and_background_icon); //calc_vertex_for_icons(4, m_del_icon); calc_vertex_for_icons(0, m_del_icon); - calc_vertex_for_icons(1, m_orient_icon); - calc_vertex_for_icons(2, m_arrange_icon); - calc_vertex_for_icons(3, m_lock_icon); - calc_vertex_for_icons(4, m_plate_settings_icon); + calc_vertex_for_icons(1, m_orient_icon); + calc_vertex_for_icons(2, m_arrange_icon); + calc_vertex_for_icons(3, m_lock_icon); + calc_vertex_for_icons(4, m_plate_settings_icon); //calc_vertex_for_number(0, (m_plate_index < 9), m_plate_idx_icon); calc_vertex_for_number(0, false, m_plate_idx_icon); // calc vertex for plate name @@ -2537,8 +2439,6 @@ bool PartPlate::set_shape(const Pointfs& shape, const Pointfs& exclude_areas, Ve calc_height_limit(); - release_opengl_resource(); - return true; } @@ -2583,42 +2483,52 @@ bool PartPlate::intersects(const BoundingBoxf3& bb) const return print_volume.intersects(bb); } -void PartPlate::render(bool bottom, bool only_body, bool force_background_color, HeightLimitMode mode, int hover_id, bool render_cali) +void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_body, bool force_background_color, HeightLimitMode mode, int hover_id, bool render_cali) { - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnable(GL_DEPTH_TEST)); - if (!bottom) { - // draw background - render_background(force_background_color); + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - render_exclude_area(force_background_color); - } + shader->set_uniform("view_model_matrix", view_matrix); + shader->set_uniform("projection_matrix", projection_matrix); - render_grid(bottom); + if (!bottom) { + // draw background + render_background(force_background_color); - if (!bottom && m_selected && !force_background_color) { - if (m_partplate_list) - render_logo(bottom, m_partplate_list->render_cali_logo && render_cali); - else - render_logo(bottom); - } + render_exclude_area(force_background_color); + } - render_height_limit(mode); + render_grid(bottom); - render_icons(bottom, only_body, hover_id); - if (!force_background_color){ - render_only_numbers(bottom); - } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisable(GL_BLEND)); + render_height_limit(mode); - //if (with_label) { - // render_label(canvas); - //} - glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_BLEND)); + + // if (with_label) { + // render_label(canvas); + // } + + shader->stop_using(); + } + + if (!bottom && m_selected && !force_background_color) { + if (m_partplate_list) + render_logo(bottom, m_partplate_list->render_cali_logo && render_cali); + else + render_logo(bottom); + } + + render_icons(bottom, only_body, hover_id); + if (!force_background_color) { + render_only_numbers(bottom); + } + + glsafe(::glDisable(GL_DEPTH_TEST)); } void PartPlate::set_selected() { @@ -3180,10 +3090,6 @@ void PartPlateList::release_icon_textures() part.texture->reset(); delete part.texture; } - if (part.vbo_id != 0) { - glsafe(::glDeleteBuffers(1, &part.vbo_id)); - part.vbo_id = 0; - } if (part.buffer) { delete part.buffer; } @@ -4437,7 +4343,7 @@ void PartPlateList::postprocess_arrange_polygon(arrangement::ArrangePolygon& arr /*rendering related functions*/ //render -void PartPlateList::render(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) +void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) { const std::lock_guard local_lock(m_plates_mutex); std::vector::iterator it = m_plate_list.begin(); @@ -4462,28 +4368,19 @@ void PartPlateList::render(bool bottom, bool only_current, bool only_body, int h if (current_index == m_current_plate) { PartPlate::HeightLimitMode height_mode = (only_current)?PartPlate::HEIGHT_LIMIT_NONE:m_height_limit_mode; if (plate_hover_index == current_index) - (*it)->render(bottom, only_body, false, height_mode, plate_hover_action, render_cali); + (*it)->render(view_matrix, projection_matrix, bottom, only_body, false, height_mode, plate_hover_action, render_cali); else - (*it)->render(bottom, only_body, false, height_mode, -1, render_cali); + (*it)->render(view_matrix, projection_matrix, bottom, only_body, false, height_mode, -1, render_cali); } else { if (plate_hover_index == current_index) - (*it)->render(bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, plate_hover_action, render_cali); + (*it)->render(view_matrix, projection_matrix, bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, plate_hover_action, render_cali); else - (*it)->render(bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, -1, render_cali); + (*it)->render(view_matrix, projection_matrix, bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, -1, render_cali); } } } -void PartPlateList::render_for_picking_pass() -{ - const std::lock_guard local_lock(m_plates_mutex); - std::vector::iterator it = m_plate_list.begin(); - for (it = m_plate_list.begin(); it != m_plate_list.end(); it++) { - (*it)->render_for_picking(); - } -} - /*int PartPlateList::select_plate_by_hover_id(int hover_id) { int index = hover_id / PartPlate::GRABBER_COUNT; @@ -5096,19 +4993,11 @@ void PartPlateList::BedTextureInfo::TexturePart::update_buffer() } if (!buffer) - buffer = new GeometryBuffer(); + buffer = new GLModel(); - if (buffer->set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z + 0.02f)) { - if (vbo_id != 0) { - glsafe(::glDeleteBuffers(1, &vbo_id)); - vbo_id = 0; - } - unsigned int* vbo_id_ptr = const_cast(&vbo_id); - glsafe(::glGenBuffers(1, vbo_id_ptr)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id_ptr)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)buffer->get_vertices_data_size(), (const GLvoid*)buffer->get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } else { + buffer->reset(); + + if (!init_model_from_poly(*buffer, poly, GROUND_Z + 0.02f)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create buffer triangles\n"; } } diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index e2b2a3bf1d..ced713a1f0 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -21,6 +21,7 @@ #include "3DScene.hpp" #include "GLModel.hpp" #include "3DBed.hpp" +#include "MeshUtils.hpp" class GLUquadric; typedef class GLUquadric GLUquadricObject; @@ -115,31 +116,22 @@ private: Transform3d m_grabber_trans_matrix; Slic3r::Geometry::Transformation position; std::vector positions; - unsigned int m_vbo_id{ 0 }; - GeometryBuffer m_triangles; - GeometryBuffer m_exclude_triangles; - GeometryBuffer m_logo_triangles; - GeometryBuffer m_gridlines; - GeometryBuffer m_gridlines_bolder; - GeometryBuffer m_height_limit_common; - GeometryBuffer m_height_limit_bottom; - GeometryBuffer m_height_limit_top; - GeometryBuffer m_del_icon; - //GeometryBuffer m_del_and_background_icon; - mutable unsigned int m_del_vbo_id{ 0 }; - GeometryBuffer m_arrange_icon; - mutable unsigned int m_arrange_vbo_id{ 0 }; - GeometryBuffer m_orient_icon; - mutable unsigned int m_orient_vbo_id{ 0 }; - GeometryBuffer m_lock_icon; - mutable unsigned int m_lock_vbo_id{ 0 }; - GeometryBuffer m_plate_settings_icon; - mutable unsigned int m_plate_settings_vbo_id{ 0 }; - GeometryBuffer m_plate_idx_icon; - mutable unsigned int m_plate_idx_vbo_id{ 0 }; + PickingModel m_triangles; + GLModel m_exclude_triangles; + GLModel m_logo_triangles; + GLModel m_gridlines; + GLModel m_gridlines_bolder; + GLModel m_height_limit_common; + GLModel m_height_limit_bottom; + GLModel m_height_limit_top; + PickingModel m_del_icon; + PickingModel m_arrange_icon; + PickingModel m_orient_icon; + PickingModel m_lock_icon; + PickingModel m_plate_settings_icon; + GLModel m_plate_idx_icon; GLTexture m_texture; - mutable float m_grabber_color[4]; float m_scale_factor{ 1.0f }; GLUquadricObject* m_quadric; int m_hover_id; @@ -152,8 +144,7 @@ private: // SoftFever // part plate name std::string m_name; - GeometryBuffer m_plate_name_icon; - mutable unsigned int m_plate_name_vbo_id{ 0 }; + GLModel m_plate_name_icon; GLTexture m_name_texture; wxCoord m_name_texture_width; wxCoord m_name_texture_height; @@ -168,48 +159,46 @@ private: void calc_exclude_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& pp_bbox); void calc_height_limit(); - void calc_vertex_for_number(int index, bool one_number, GeometryBuffer &buffer); - void calc_vertex_for_icons(int index, GeometryBuffer &buffer); - void calc_vertex_for_icons_background(int icon_count, GeometryBuffer &buffer); - void render_background(bool force_default_color = false) const; - void render_logo(bool bottom, bool render_cali = true) const; - void render_logo_texture(GLTexture& logo_texture, const GeometryBuffer& logo_buffer, bool bottom, unsigned int vbo_id) const; - void render_exclude_area(bool force_default_color) const; - //void render_background_for_picking(const float* render_color) const; - void render_grid(bool bottom) const; - void render_height_limit(PartPlate::HeightLimitMode mode = HEIGHT_LIMIT_BOTH) const; - void render_label(GLCanvas3D& canvas) const; - void render_grabber(const float* render_color, bool use_lighting) const; - void render_face(float x_size, float y_size) const; - void render_arrows(const float* render_color, bool use_lighting) const; - void render_left_arrow(const float* render_color, bool use_lighting) const; - void render_right_arrow(const float* render_color, bool use_lighting) const; - void render_icon_texture(int position_id, int tex_coords_id, const GeometryBuffer &buffer, GLTexture &texture, unsigned int &vbo_id) const; + void calc_vertex_for_number(int index, bool one_number, GLModel &buffer); + void calc_vertex_for_icons(int index, PickingModel &model); + // void calc_vertex_for_icons_background(int icon_count, GLModel &buffer); + void render_background(bool force_default_color = false); + void render_logo(bool bottom, bool render_cali = true); + void render_logo_texture(GLTexture &logo_texture, GLModel &logo_buffer, bool bottom); + void render_exclude_area(bool force_default_color); + //void render_background_for_picking(const ColorRGBA render_color) const; + void render_grid(bool bottom); + void render_height_limit(PartPlate::HeightLimitMode mode = HEIGHT_LIMIT_BOTH); + // void render_label(GLCanvas3D& canvas) const; + // void render_grabber(const ColorRGBA render_color, bool use_lighting) const; + // void render_face(float x_size, float y_size) const; + // void render_arrows(const ColorRGBA render_color, bool use_lighting) const; + // void render_left_arrow(const ColorRGBA render_color, bool use_lighting) const; + // void render_right_arrow(const ColorRGBA render_color, bool use_lighting) const; + void render_icon_texture(GLModel &buffer, GLTexture &texture); void show_tooltip(const std::string tooltip); void render_icons(bool bottom, bool only_name = false, int hover_id = -1); - void render_only_numbers(bool bottom) const; - void render_plate_name_texture(int position_id, int tex_coords_id); - void render_rectangle_for_picking(const GeometryBuffer &buffer, const float* render_color) const; - void on_render_for_picking() const; - std::array picking_color_component(int idx) const; - void release_opengl_resource(); + void render_only_numbers(bool bottom); + void render_plate_name_texture(); + void register_raycasters_for_picking(GLCanvas3D& canvas); + int picking_id_component(int idx) const; public: static const unsigned int PLATE_BASE_ID = 255 * 255 * 253; static const unsigned int PLATE_NAME_HOVER_ID = 6; static const unsigned int GRABBER_COUNT = 7; - static std::array SELECT_COLOR; - static std::array UNSELECT_COLOR; - static std::array UNSELECT_DARK_COLOR; - static std::array DEFAULT_COLOR; - static std::array LINE_BOTTOM_COLOR; - static std::array LINE_TOP_COLOR; - static std::array LINE_TOP_DARK_COLOR; - static std::array LINE_TOP_SEL_COLOR; - static std::array LINE_TOP_SEL_DARK_COLOR; - static std::array HEIGHT_LIMIT_BOTTOM_COLOR; - static std::array HEIGHT_LIMIT_TOP_COLOR; + static ColorRGBA SELECT_COLOR; + static ColorRGBA UNSELECT_COLOR; + static ColorRGBA UNSELECT_DARK_COLOR; + static ColorRGBA DEFAULT_COLOR; + static ColorRGBA LINE_BOTTOM_COLOR; + static ColorRGBA LINE_TOP_COLOR; + static ColorRGBA LINE_TOP_DARK_COLOR; + static ColorRGBA LINE_TOP_SEL_COLOR; + static ColorRGBA LINE_TOP_SEL_DARK_COLOR; + static ColorRGBA HEIGHT_LIMIT_BOTTOM_COLOR; + static ColorRGBA HEIGHT_LIMIT_TOP_COLOR; static void update_render_colors(); static void load_render_colors(); @@ -350,8 +339,8 @@ public: bool contains(const BoundingBoxf3& bb) const; bool intersects(const BoundingBoxf3& bb) const; - void render(bool bottom, bool only_body = false, bool force_background_color = false, HeightLimitMode mode = HEIGHT_LIMIT_NONE, int hover_id = -1, bool render_cali = false); - void render_for_picking() const { on_render_for_picking(); } + void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_body = false, bool force_background_color = false, HeightLimitMode mode = HEIGHT_LIMIT_NONE, int hover_id = -1, bool render_cali = false); + void set_selected(); void set_unselected(); void set_hover_id(int id) { m_hover_id = id; } @@ -569,18 +558,16 @@ public: float y; float w; float h; - unsigned int vbo_id; std::string filename; GLTexture* texture { nullptr }; Vec2d offset; - GeometryBuffer* buffer { nullptr }; + GLModel* buffer { nullptr }; TexturePart(float xx, float yy, float ww, float hh, std::string file){ x = xx; y = yy; w = ww; h = hh; filename = file; texture = nullptr; buffer = nullptr; - vbo_id = 0; offset = Vec2d(0, 0); } @@ -593,7 +580,6 @@ public: this->buffer = part.buffer; this->filename = part.filename; this->texture = part.texture; - this->vbo_id = part.vbo_id; } void update_buffer(); @@ -752,10 +738,14 @@ public: /*rendering related functions*/ void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; } - void render(bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false); - void render_for_picking_pass(); + void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false); void set_render_option(bool bedtype_texture, bool plate_settings); void set_render_cali(bool value = true) { render_cali_logo = value; } + void register_raycasters_for_picking(GLCanvas3D& canvas) + { + for (auto plate : m_plate_list) + plate->register_raycasters_for_picking(canvas); + } BoundingBoxf3& get_bounding_box() { return m_bounding_box; } //int select_plate_by_hover_id(int hover_id); int select_plate_by_obj(int obj_index, int instance_index); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 44b44801a9..c76307b130 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1,3 +1,22 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Vojtěch Král @vojtechkral +///|/ Copyright (c) 2022 Michael Kirsch +///|/ Copyright (c) 2021 Boleslaw Ciesielski +///|/ Copyright (c) 2019 John Drake @foxox +///|/ +///|/ ported from lib/Slic3r/GUI/Plater.pm: +///|/ Copyright (c) Prusa Research 2016 - 2019 Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Tomáš Mészáros @tamasmeszaros +///|/ Copyright (c) 2018 Martin Loidl @LoidlM +///|/ Copyright (c) 2017 Matthias Gazzari @qtux +///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2017 Joseph Lenox @lordofhyphens +///|/ Copyright (c) 2015 Daren Schwenke +///|/ Copyright (c) 2014 Mark Hindess +///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake +///|/ Copyright (c) 2012 Henrik Brix Andersen @henrikbrixandersen +///|/ Copyright (c) 2012 Sam Wong +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "Plater.hpp" #include "libslic3r/Config.hpp" @@ -128,6 +147,7 @@ #include "Gizmos/GLGizmosManager.hpp" #endif // __APPLE__ +#include #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" @@ -4040,7 +4060,7 @@ int Plater::priv::get_selected_volume_idx() const int idx = selection.get_object_idx(); if ((0 > idx) || (idx > 1000)) return-1; - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); if (model.objects[idx]->volumes.size() > 1) return v->volume_idx(); return -1; @@ -4851,7 +4871,7 @@ void Plater::priv::replace_with_stl() if (selection.is_wipe_tower() || get_selection().get_volume_idxs().size() != 1) return; - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); int object_idx = v->object_idx(); int volume_idx = v->volume_idx(); @@ -7161,7 +7181,7 @@ bool Plater::priv::can_edit_text() const return true; if (selection.is_single_volume()) { - const GLVolume *gl_volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume *gl_volume = selection.get_first_volume(); int out_object_idx = gl_volume->object_idx(); ModelObject * model_object = selection.get_model()->objects[out_object_idx]; int out_volume_idx = gl_volume->volume_idx(); @@ -8280,14 +8300,6 @@ void Plater::add_model(bool imperial_units, std::string fname) wxGetApp().mainframe->update_title(); } } -std::array get_cut_plane(const BoundingBoxf3& bbox, const double& cut_height) { - std::array plane_pts; - plane_pts[0] = Vec3d(bbox.min(0), bbox.min(1), cut_height); - plane_pts[1] = Vec3d(bbox.max(0), bbox.min(1), cut_height); - plane_pts[2] = Vec3d(bbox.max(0), bbox.max(1), cut_height); - plane_pts[3] = Vec3d(bbox.min(0), bbox.max(1), cut_height); - return plane_pts; -} void Plater::calib_pa(const Calib_Params& params) { @@ -8410,6 +8422,25 @@ void Plater::_calib_pa_pattern(const Calib_Params& params) changed_objects({ 0 }); } +void Plater::cut_horizontal(size_t obj_idx, size_t instance_idx, double z, ModelObjectCutAttributes attributes) +{ + wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); + auto *object = p->model.objects[obj_idx]; + + wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); + + if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) + return; + + wxBusyCursor wait; + + const Vec3d instance_offset = object->instances[instance_idx]->get_offset(); + Cut cut(object, instance_idx, Geometry::translation_transform(z * Vec3d::UnitZ() - instance_offset), attributes); + const auto new_objects = cut.perform_with_plane(); + + apply_cut_object_to_model(obj_idx, new_objects); +} + void Plater::_calib_pa_tower(const Calib_Params& params) { add_model(false, Slic3r::resources_dir() + "/calib/pressure_advance/tower_with_seam.stl"); @@ -8446,8 +8477,7 @@ void Plater::_calib_pa_tower(const Calib_Params& params) { auto new_height = std::ceil((params.end - params.start) / params.step) + 1; auto obj_bb = model().objects[0]->bounding_box(); if (new_height < obj_bb.size().z()) { - std::array plane_pts = get_cut_plane(obj_bb, new_height); - cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_horizontal(0, 0, new_height, ModelObjectCutAttribute::KeepLower); } _calib_pa_select_added_objects(); @@ -8588,8 +8618,7 @@ void Plater::calib_temp(const Calib_Params& params) { // add EPSILON offset to avoid cutting at the exact location where the flat surface is auto new_height = block_count * 10.0 + EPSILON; if (new_height < obj_bb.size().z()) { - std::array plane_pts = get_cut_plane(obj_bb, new_height); - cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_horizontal(0, 0, new_height, ModelObjectCutAttribute::KeepLower); } } @@ -8599,8 +8628,7 @@ void Plater::calib_temp(const Calib_Params& params) { if(block_count > 0){ auto new_height = block_count * 10.0 + EPSILON; if (new_height < obj_bb.size().z()) { - std::array plane_pts = get_cut_plane(obj_bb, new_height); - cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepUpper); + cut_horizontal(0, 0, new_height, ModelObjectCutAttribute::KeepUpper); } } @@ -8668,8 +8696,7 @@ void Plater::calib_max_vol_speed(const Calib_Params& params) auto obj_bb = obj->bounding_box(); auto height = (params.end - params.start + 1) / params.step; if (height < obj_bb.size().z()) { - std::array plane_pts = get_cut_plane(obj_bb, height); - cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower); } auto new_params = params; @@ -8717,8 +8744,7 @@ void Plater::calib_retraction(const Calib_Params& params) auto obj_bb = obj->bounding_box(); auto height = 1.0 + 0.4 + ((params.end - params.start)) / params.step; if (height < obj_bb.size().z()) { - std::array plane_pts = get_cut_plane(obj_bb, height); - cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower); } p->background_process.fff_print()->set_calib_params(params); @@ -8759,8 +8785,7 @@ void Plater::calib_VFA(const Calib_Params& params) auto obj_bb = model().objects[0]->bounding_box(); auto height = 5 * ((params.end - params.start) / params.step + 1); if (height < obj_bb.size().z()) { - std::array plane_pts = get_cut_plane(obj_bb, height); - cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower); } p->background_process.fff_print()->set_calib_params(params); @@ -9865,27 +9890,16 @@ void Plater::convert_unit(ConversionType conv_type) } } -// BBS: replace z with plane_points -void Plater::cut(size_t obj_idx, size_t instance_idx, std::array plane_points, ModelObjectCutAttributes attributes) +void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects) { - wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); - auto *object = p->model.objects[obj_idx]; + model().delete_object(obj_idx); + sidebar().obj_list()->delete_object_from_list(obj_idx); - wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - - if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) - return; - - wxBusyCursor wait; - // BBS: replace z with plane_points - const auto new_objects = object->cut(instance_idx, plane_points, attributes); - - remove(obj_idx); - p->load_model_objects(new_objects); + // suppress to call selection update for Object List to avoid call of early Gizmos on/off update + p->load_model_objects(new_objects, false, false); // now process all updates of the 3d scene update(); - // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call for (size_t idx = 0; idx < p->model.objects.size(); idx++) @@ -9895,62 +9909,10 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, std::array plane size_t last_id = p->model.objects.size() - 1; for (size_t i = 0; i < new_objects.size(); ++i) selection.add_object((unsigned int)(last_id - i), i == 0); -} -// BBS -void Plater::segment(size_t obj_idx, size_t instance_idx, double smoothing_alpha, int segment_number) -{ - wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); - auto* object = p->model.objects[obj_idx]; - - wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - - Plater::TakeSnapshot snapshot(this, "Segment"); - - wxBusyCursor wait; - // real process - PresetBundle& preset_bundle = *wxGetApp().preset_bundle; - const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); - const size_t filament_cnt = print_tech != ptFFF ? 1 : preset_bundle.filament_presets.size(); - const auto new_objects = object->segment(instance_idx, filament_cnt, smoothing_alpha, segment_number); - - remove(obj_idx); - p->load_model_objects(new_objects); - - Selection& selection = p->get_selection(); - size_t last_id = p->model.objects.size() - 1; - for (size_t i = 0; i < new_objects.size(); ++i) - { - selection.add_object((unsigned int)(last_id - i), i == 0); - } -} - -// BBS -void Plater::merge(size_t obj_idx, std::vector& vol_indeces) -{ - wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); - auto* object = p->model.objects[obj_idx]; - - Plater::TakeSnapshot snapshot(this, "Merge"); - - wxBusyCursor wait; - // real process - PresetBundle& preset_bundle = *wxGetApp().preset_bundle; - const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); - // BBS - const size_t filament_cnt = print_tech != ptFFF ? 1 : preset_bundle.filament_presets.size(); - - const auto new_objects = object->merge_volumes(vol_indeces); - - remove(obj_idx); - p->load_model_objects(new_objects); - - Selection& selection = p->get_selection(); - size_t last_id = p->model.objects.size() - 1; - for (size_t i = 0; i < new_objects.size(); ++i) - { - selection.add_object((unsigned int)(last_id - i), i == 0); - } + // UIThreadWorker w; + // arrange(w, true); + // w.wait_for_idle(); } void Plater::export_gcode(bool prefer_removable) @@ -10341,7 +10303,7 @@ void Plater::export_stl(bool extended, bool selection_only) if (selection.get_mode() == Selection::Instance) mesh = mesh_to_export(*model_object, (model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); else { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); mesh = model_object->volumes[volume->volume_idx()]->mesh(); mesh.transform(volume->get_volume_transformation().get_matrix(), true); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 62c505f5ad..809a3ed801 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -1,3 +1,19 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Vojtěch Král @vojtechkral +///|/ +///|/ ported from lib/Slic3r/GUI/Plater.pm: +///|/ Copyright (c) Prusa Research 2016 - 2019 Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Tomáš Mészáros @tamasmeszaros +///|/ Copyright (c) 2018 Martin Loidl @LoidlM +///|/ Copyright (c) 2017 Matthias Gazzari @qtux +///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2017 Joseph Lenox @lordofhyphens +///|/ Copyright (c) 2015 Daren Schwenke +///|/ Copyright (c) 2014 Mark Hindess +///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake +///|/ Copyright (c) 2012 Henrik Brix Andersen @henrikbrixandersen +///|/ Copyright (c) 2012 Sam Wong +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_Plater_hpp_ #define slic3r_Plater_hpp_ @@ -25,6 +41,7 @@ #include "libslic3r/PrintBase.hpp" #include "libslic3r/calib.hpp" +#include "libslic3r/CutUtils.hpp" #define FILAMENT_SYSTEM_COLORS_NUM 16 @@ -41,8 +58,6 @@ class BuildVolume; enum class BuildVolume_Type : unsigned char; class Model; class ModelObject; -enum class ModelObjectCutAttribute : int; -using ModelObjectCutAttributes = enum_bitmask; class ModelInstance; class Print; class SLAPrint; @@ -323,12 +338,7 @@ public: void scale_selection_to_fit_print_volume(); void convert_unit(ConversionType conv_type); - // BBS: replace z with plane_points - void cut(size_t obj_idx, size_t instance_idx, std::array plane_points, ModelObjectCutAttributes attributes); - - // BBS: segment model with CGAL - void segment(size_t obj_idx, size_t instance_idx, double smoothing_alpha=0.5, int segment_number=5); - void merge(size_t obj_idx, std::vector& vol_indeces); + void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); void send_to_printer(bool isall = false); void export_gcode(bool prefer_removable); @@ -742,6 +752,8 @@ private: void _calib_pa_tower(const Calib_Params& params); void _calib_pa_select_added_objects(); + void cut_horizontal(size_t obj_idx, size_t instance_idx, double z, ModelObjectCutAttributes attributes); + friend class SuppressBackgroundProcessingUpdate; }; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index f00b5c4612..c58150b12a 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -24,6 +24,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp new file mode 100644 index 0000000000..96be46de29 --- /dev/null +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -0,0 +1,318 @@ +///|/ Copyright (c) Prusa Research 2022 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "libslic3r/libslic3r.h" +#include "SceneRaycaster.hpp" + +#include "Camera.hpp" +#include "GUI_App.hpp" +#include "Selection.hpp" +#include "Plater.hpp" + +namespace Slic3r { +namespace GUI { + +SceneRaycaster::SceneRaycaster() { +#if ENABLE_RAYCAST_PICKING_DEBUG + // hit point + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 16.0)); + m_sphere.set_color(ColorRGBA::YELLOW()); + + // hit normal + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)Vec3f::Zero()); + init_data.add_vertex((Vec3f)Vec3f::UnitZ()); + + // indices + init_data.add_line(0, 1); + + m_line.init_from(std::move(init_data)); +#endif // ENABLE_RAYCAST_PICKING_DEBUG +} + +std::shared_ptr SceneRaycaster::add_raycaster(EType type, int id, const MeshRaycaster& raycaster, + const Transform3d& trafo, bool use_back_faces) +{ + switch (type) { + case EType::Bed: { return m_bed.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::Volume: { return m_volumes.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::FallbackGizmo: { return m_fallback_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + default: { assert(false); return nullptr; } + }; +} + +void SceneRaycaster::remove_raycasters(EType type, int id) +{ + std::vector>* raycasters = get_raycasters(type); + auto it = raycasters->begin(); + while (it != raycasters->end()) { + if ((*it)->get_id() == encode_id(type, id)) + it = raycasters->erase(it); + else + ++it; + } +} + +void SceneRaycaster::remove_raycasters(EType type) +{ + switch (type) { + case EType::Bed: { m_bed.clear(); break; } + case EType::Volume: { m_volumes.clear(); break; } + case EType::Gizmo: { m_gizmos.clear(); break; } + case EType::FallbackGizmo: { m_fallback_gizmos.clear(); break; } + default: { break; } + }; +} + +void SceneRaycaster::remove_raycaster(std::shared_ptr item) +{ + for (auto it = m_bed.begin(); it != m_bed.end(); ++it) { + if (*it == item) { + m_bed.erase(it); + return; + } + } + for (auto it = m_volumes.begin(); it != m_volumes.end(); ++it) { + if (*it == item) { + m_volumes.erase(it); + return; + } + } + for (auto it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { + if (*it == item) { + m_gizmos.erase(it); + return; + } + } + for (auto it = m_fallback_gizmos.begin(); it != m_fallback_gizmos.end(); ++it) { + if (*it == item) { + m_fallback_gizmos.erase(it); + return; + } + } +} + +SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const +{ + // helper class used to return currently selected volume as hit when overlapping with other volumes + // to allow the user to click and drag on a selected volume + class VolumeKeeper + { + std::optional m_selected_volume_id; + Vec3f m_closest_hit_pos{ std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max() }; + bool m_selected_volume_already_found{ false }; + + public: + VolumeKeeper() { + const Selection& selection = wxGetApp().plater()->get_selection(); + if (selection.is_single_volume() || selection.is_single_modifier()) { + const GLVolume* volume = selection.get_first_volume(); + if (!volume->is_wipe_tower && !volume->is_sla_pad() && !volume->is_sla_support()) + m_selected_volume_id = *selection.get_volume_idxs().begin(); + } + } + + bool is_active() const { return m_selected_volume_id.has_value(); } + const Vec3f& get_closest_hit_pos() const { return m_closest_hit_pos; } + bool check_hit_result(const HitResult& hit) { + assert(is_active()); + + if (m_selected_volume_already_found && hit.type == SceneRaycaster::EType::Volume && hit.position.isApprox(m_closest_hit_pos)) + return false; + + if (hit.type == SceneRaycaster::EType::Volume) + m_selected_volume_already_found = *m_selected_volume_id == (unsigned int)decode_id(hit.type, hit.raycaster_id); + + m_closest_hit_pos = hit.position; + return true; + } + }; + + VolumeKeeper volume_keeper; + + double closest_hit_squared_distance = std::numeric_limits::max(); + auto is_closest = [&closest_hit_squared_distance, &volume_keeper](const Camera& camera, const Vec3f& hit) { + const double hit_squared_distance = (camera.get_position() - hit.cast()).squaredNorm(); + bool ret = hit_squared_distance < closest_hit_squared_distance; + if (volume_keeper.is_active()) + ret |= hit.isApprox(volume_keeper.get_closest_hit_pos()); + if (ret) + closest_hit_squared_distance = hit_squared_distance; + return ret; + }; + +#if ENABLE_RAYCAST_PICKING_DEBUG + const_cast*>(&m_last_hit)->reset(); +#endif // ENABLE_RAYCAST_PICKING_DEBUG + + HitResult ret; + + auto test_raycasters = [this, is_closest, clipping_plane, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) { + const ClippingPlane* clip_plane = (clipping_plane != nullptr && type == EType::Volume) ? clipping_plane : nullptr; + const std::vector>* raycasters = get_raycasters(type); + const Vec3f camera_forward = camera.get_dir_forward().cast(); + HitResult current_hit = { type }; + for (std::shared_ptr item : *raycasters) { + if (!item->is_active()) + continue; + + current_hit.raycaster_id = item->get_id(); + const Transform3d& trafo = item->get_transform(); + if (item->get_raycaster()->closest_hit(mouse_pos, trafo, camera, current_hit.position, current_hit.normal, clip_plane)) { + current_hit.position = (trafo * current_hit.position.cast()).cast(); + current_hit.normal = (trafo.matrix().block(0, 0, 3, 3).inverse().transpose() * current_hit.normal.cast()).normalized().cast(); + if (item->use_back_faces() || current_hit.normal.dot(camera_forward) < 0.0f) { + if (is_closest(camera, current_hit.position)) { + if (volume_keeper.is_active()) { + if (volume_keeper.check_hit_result(current_hit)) + ret = current_hit; + } + else + ret = current_hit; + } + } + } + } + }; + + if (!m_gizmos.empty()) + test_raycasters(EType::Gizmo, mouse_pos, camera, ret); + + if (!m_fallback_gizmos.empty() && !ret.is_valid()) + test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret); + + if (!m_gizmos_on_top || !ret.is_valid()) { + if (camera.is_looking_downward() && !m_bed.empty()) + test_raycasters(EType::Bed, mouse_pos, camera, ret); + if (!m_volumes.empty()) + test_raycasters(EType::Volume, mouse_pos, camera, ret); + } + + if (ret.is_valid()) + ret.raycaster_id = decode_id(ret.type, ret.raycaster_id); + +#if ENABLE_RAYCAST_PICKING_DEBUG + *const_cast*>(&m_last_hit) = ret; +#endif // ENABLE_RAYCAST_PICKING_DEBUG + return ret; +} + +#if ENABLE_RAYCAST_PICKING_DEBUG +void SceneRaycaster::render_hit(const Camera& camera) +{ + if (!m_last_hit.has_value() || !(*m_last_hit).is_valid()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + shader->start_using(); + + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + const Transform3d sphere_view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform((*m_last_hit).position.cast()) * + Geometry::scale_transform(4.0 * camera.get_inv_zoom()); + shader->set_uniform("view_model_matrix", sphere_view_model_matrix); + m_sphere.render(); + + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), (*m_last_hit).normal.cast()).toRotationMatrix(); + + const Transform3d line_view_model_matrix = sphere_view_model_matrix * m * Geometry::scale_transform(10.0); + shader->set_uniform("view_model_matrix", line_view_model_matrix); + m_line.render(); + + shader->stop_using(); +} + +size_t SceneRaycaster::active_beds_count() const { + size_t count = 0; + for (const auto& b : m_bed) { + if (b->is_active()) + ++count; + } + return count; +} +size_t SceneRaycaster::active_volumes_count() const { + size_t count = 0; + for (const auto& v : m_volumes) { + if (v->is_active()) + ++count; + } + return count; +} +size_t SceneRaycaster::active_gizmos_count() const { + size_t count = 0; + for (const auto& g : m_gizmos) { + if (g->is_active()) + ++count; + } + return count; +} +size_t SceneRaycaster::active_fallback_gizmos_count() const { + size_t count = 0; + for (const auto& g : m_fallback_gizmos) { + if (g->is_active()) + ++count; + } + return count; +} +#endif // ENABLE_RAYCAST_PICKING_DEBUG + +std::vector>* SceneRaycaster::get_raycasters(EType type) +{ + std::vector>* ret = nullptr; + switch (type) + { + case EType::Bed: { ret = &m_bed; break; } + case EType::Volume: { ret = &m_volumes; break; } + case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } + default: { break; } + } + assert(ret != nullptr); + return ret; +} + +const std::vector>* SceneRaycaster::get_raycasters(EType type) const +{ + const std::vector>* ret = nullptr; + switch (type) + { + case EType::Bed: { ret = &m_bed; break; } + case EType::Volume: { ret = &m_volumes; break; } + case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } + default: { break; } + } + assert(ret != nullptr); + return ret; +} + +int SceneRaycaster::base_id(EType type) +{ + switch (type) + { + case EType::Bed: { return int(EIdBase::Bed); } + case EType::Volume: { return int(EIdBase::Volume); } + case EType::Gizmo: { return int(EIdBase::Gizmo); } + case EType::FallbackGizmo: { return int(EIdBase::FallbackGizmo); } + default: { break; } + }; + + assert(false); + return -1; +} + +int SceneRaycaster::encode_id(EType type, int id) { return base_id(type) + id; } +int SceneRaycaster::decode_id(EType type, int id) { return id - base_id(type); } + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/SceneRaycaster.hpp b/src/slic3r/GUI/SceneRaycaster.hpp new file mode 100644 index 0000000000..778ec0ab43 --- /dev/null +++ b/src/slic3r/GUI/SceneRaycaster.hpp @@ -0,0 +1,126 @@ +///|/ Copyright (c) Prusa Research 2022 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_SceneRaycaster_hpp_ +#define slic3r_SceneRaycaster_hpp_ + +#include "MeshUtils.hpp" +#include "GLModel.hpp" +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +struct Camera; + +class SceneRaycasterItem +{ + int m_id{ -1 }; + bool m_active{ true }; + bool m_use_back_faces{ false }; + const MeshRaycaster* m_raycaster; + Transform3d m_trafo; + +public: + SceneRaycasterItem(int id, const MeshRaycaster& raycaster, const Transform3d& trafo, bool use_back_faces = false) + : m_id(id), m_raycaster(&raycaster), m_trafo(trafo), m_use_back_faces(use_back_faces) + {} + + int get_id() const { return m_id; } + bool is_active() const { return m_active; } + void set_active(bool active) { m_active = active; } + bool use_back_faces() const { return m_use_back_faces; } + const MeshRaycaster* get_raycaster() const { return m_raycaster; } + const Transform3d& get_transform() const { return m_trafo; } + void set_transform(const Transform3d& trafo) { m_trafo = trafo; } +}; + +class SceneRaycaster +{ +public: + enum class EType + { + None, + Bed, + Volume, + Gizmo, + FallbackGizmo // Is used for gizmo grabbers which will be hit after all grabbers of Gizmo type + }; + + enum class EIdBase + { + Bed = 0, + Volume = 1000, + Gizmo = 1000000, + FallbackGizmo = 2000000 + }; + + struct HitResult + { + EType type{ EType::None }; + int raycaster_id{ -1 }; + Vec3f position{ Vec3f::Zero() }; + Vec3f normal{ Vec3f::Zero() }; + + bool is_valid() const { return raycaster_id != -1; } + }; + +private: + std::vector> m_bed; + std::vector> m_volumes; + std::vector> m_gizmos; + std::vector> m_fallback_gizmos; + + // When set to true, if checking gizmos returns a valid hit, + // the search is not performed on other types + bool m_gizmos_on_top{ false }; + +#if ENABLE_RAYCAST_PICKING_DEBUG + GLModel m_sphere; + GLModel m_line; + std::optional m_last_hit; +#endif // ENABLE_RAYCAST_PICKING_DEBUG + +public: + SceneRaycaster(); + + std::shared_ptr add_raycaster(EType type, int picking_id, const MeshRaycaster& raycaster, + const Transform3d& trafo, bool use_back_faces = false); + void remove_raycasters(EType type, int id); + void remove_raycasters(EType type); + void remove_raycaster(std::shared_ptr item); + + std::vector>* get_raycasters(EType type); + const std::vector>* get_raycasters(EType type) const; + + void set_gizmos_on_top(bool value) { m_gizmos_on_top = value; } + + HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr) const; + +#if ENABLE_RAYCAST_PICKING_DEBUG + void render_hit(const Camera& camera); + + size_t beds_count() const { return m_bed.size(); } + size_t volumes_count() const { return m_volumes.size(); } + size_t gizmos_count() const { return m_gizmos.size(); } + size_t fallback_gizmos_count() const { return m_fallback_gizmos.size(); } + size_t active_beds_count() const; + size_t active_volumes_count() const; + size_t active_gizmos_count() const; + size_t active_fallback_gizmos_count() const; +#endif // ENABLE_RAYCAST_PICKING_DEBUG + + static int decode_id(EType type, int id); + +private: + static int encode_id(EType type, int id); + static int base_id(EType type); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_SceneRaycaster_hpp_ diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index 2119632d38..1d581aef9b 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Thread.hpp" +#include "libslic3r/Color.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Preview.hpp" @@ -3643,7 +3644,6 @@ void SelectMachineDialog::set_default_normal() //init MaterialItem auto extruders = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_used_extruders(); - BitmapCache bmcache; MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { @@ -3661,10 +3661,10 @@ void SelectMachineDialog::set_default_normal() for (auto i = 0; i < extruders.size(); i++) { auto extruder = extruders[i] - 1; auto colour = wxGetApp().preset_bundle->project_config.opt_string("filament_colour", (unsigned int) extruder); - unsigned char rgb[4]; - bmcache.parse_color4(colour, rgb); + ColorRGBA rgb; + decode_color(colour, rgb); - auto colour_rgb = wxColour((int) rgb[0], (int) rgb[1], (int) rgb[2], (int) rgb[3]); + auto colour_rgb = wxColour((int) rgb.r_uchar(), (int) rgb.g_uchar(), (int) rgb.b_uchar(), (int) rgb.a_uchar()); if (extruder >= materials.size() || extruder < 0 || extruder >= display_materials.size()) continue; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c9e8fb9b4c..9e385ab54c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1,3 +1,7 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "libslic3r/libslic3r.h" #include "Selection.hpp" @@ -23,7 +27,9 @@ #include #include -static const std::array UNIFORM_SCALE_COLOR = { 0.923f, 0.504f, 0.264f, 1.0f }; +static const Slic3r::ColorRGBA UNIFORM_SCALE_COLOR = Slic3r::ColorRGBA::ORANGE(); +static const Slic3r::ColorRGBA SOLID_PLANE_COLOR = {0.0f, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f}; +static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5f }; namespace Slic3r { namespace GUI { @@ -115,7 +121,6 @@ Selection::Selection() , m_type(Empty) , m_valid(false) , m_scale_factor(1.0f) - , m_dragging(false) { this->set_bounding_boxes_dirty(); } @@ -132,9 +137,8 @@ bool Selection::init() { m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); - #if ENABLE_RENDER_SELECTION_CENTER - m_vbo_sphere.init_from(make_sphere(0.75, 2*PI/24)); + m_vbo_sphere.init_from(its_make_sphere(0.75, PI / 12.0)); #endif // ENABLE_RENDER_SELECTION_CENTER return true; @@ -572,11 +576,11 @@ void Selection::clear() for (unsigned int i : m_list) { GLVolume& volume = *(*m_volumes)[i]; volume.selected = false; - bool transparent = volume.color[3] < 1.0f; - if (transparent) + bool is_transparent = volume.color.is_transparent(); + if (is_transparent) volume.force_transparent = true; volume.set_render_color(); - if (transparent) + if (is_transparent) volume.force_transparent = false; } #else @@ -640,6 +644,28 @@ void Selection::volumes_changed(const std::vector &map_volume_old_to_new this->set_bounding_boxes_dirty(); } +bool Selection::is_any_connector() const +{ + const int obj_idx = get_object_idx(); + + if ((is_any_volume() || is_any_modifier() || is_mixed()) && // some solid_part AND/OR modifier is selected + obj_idx >= 0 && m_model->objects[obj_idx]->is_cut()) { + const ModelVolumePtrs& obj_volumes = m_model->objects[obj_idx]->volumes; + for (size_t vol_idx = 0; vol_idx < obj_volumes.size(); vol_idx++) + if (obj_volumes[vol_idx]->is_cut_connector()) + for (const GLVolume* v : *m_volumes) + if (v->object_idx() == obj_idx && v->volume_idx() == (int)vol_idx && v->selected) + return true; + } + return false; +} + +bool Selection::is_any_cut_volume() const +{ + const int obj_idx = get_object_idx(); + return is_any_volume() && obj_idx >= 0 && m_model->objects[obj_idx]->is_cut(); +} + bool Selection::is_single_full_instance() const { if (m_type == SingleFullInstance) @@ -710,6 +736,17 @@ bool Selection::contains_any_volume(const std::vector& volume_idxs return false; } +bool Selection::contains_sinking_volumes(bool ignore_modifiers) const +{ + for (const GLVolume* v : *m_volumes) { + if (!ignore_modifiers || !v->is_modifier) { + if (v->is_sinking()) + return true; + } + } + return false; +} + bool Selection::matches(const std::vector& volume_idxs) const { unsigned int count = 0; @@ -811,12 +848,11 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } -void Selection::start_dragging() +void Selection::setup_cache() { if (!m_valid) return; - m_dragging = true; set_caches(); } @@ -1165,12 +1201,12 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume) type.set_joint(); // apply scale - start_dragging(); + setup_cache(); scale(s * Vec3d::Ones(), type); wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot // center selection on print bed - start_dragging(); + setup_cache(); offset.z() = -get_bounding_box().min.z(); translate(offset); wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot @@ -1552,66 +1588,69 @@ void Selection::erase() } } -void Selection::render(float scale_factor) const +void Selection::render(float scale_factor) { if (!m_valid || is_empty()) return; - *const_cast(&m_scale_factor) = scale_factor; - + m_scale_factor = scale_factor; // render cumulative bounding box of selected volumes - render_selected_volumes(); + render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); render_synchronized_volumes(); } #if ENABLE_RENDER_SELECTION_CENTER -void Selection::render_center(bool gizmo_is_dragging) const +void Selection::render_center(bool gizmo_is_dragging) { if (!m_valid || is_empty()) return; + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glColor3f(1.0f, 1.0f, 1.0f)); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(center(0), center(1), center(2))); + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(center); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + m_vbo_sphere.set_color(ColorRGBA::WHITE()); m_vbo_sphere.render(); - glsafe(::glPopMatrix()); + + shader->stop_using(); } #endif // ENABLE_RENDER_SELECTION_CENTER //BBS: GUI refactor, add uniform scale from gizmo -void Selection::render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale) const -//void Selection::render_sidebar_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale) +//void Selection::render_sidebar_hints(const std::string& sidebar_field) { if (sidebar_field.empty()) return; - GLShaderProgram* shader = nullptr; + GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); + if (shader == nullptr) + return; - if (!boost::starts_with(sidebar_field, "layer")) { - shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - } + shader->start_using(); glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); + const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); + Transform3d orient_matrix = Transform3d::Identity(); if (!boost::starts_with(sidebar_field, "layer")) { - const Vec3d& center = get_bounding_box().center(); - + shader->set_uniform("emission_factor", 0.05f); // BBS if (is_single_full_instance()/* && !wxGetApp().obj_manipul()->get_world_coordinates()*/) { - glsafe(::glTranslated(center(0), center(1), center(2))); if (!boost::starts_with(sidebar_field, "position")) { - Transform3d orient_matrix = Transform3d::Identity(); if (boost::starts_with(sidebar_field, "scale")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); else if (boost::starts_with(sidebar_field, "rotation")) { @@ -1619,50 +1658,45 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, bool unif orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); else if (boost::ends_with(sidebar_field, "y")) { const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); - if (rotation(0) == 0.0) + if (rotation.x() == 0.0) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); else - orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + orient_matrix.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ())); } } - - glsafe(::glMultMatrixd(orient_matrix.data())); } - } else if (is_single_volume() || is_single_modifier()) { - glsafe(::glTranslated(center(0), center(1), center(2))); - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + } + else if (is_single_volume() || is_single_modifier()) { + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); if (!boost::starts_with(sidebar_field, "position")) orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); - } else { - glsafe(::glTranslated(center(0), center(1), center(2))); - if (requires_local_axes()) { - const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); - } + } + else { + if (requires_local_axes()) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); } } + if (!boost::starts_with(sidebar_field, "layer")) + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); if (boost::starts_with(sidebar_field, "position")) - render_sidebar_position_hints(sidebar_field); + render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "rotation")) - render_sidebar_rotation_hints(sidebar_field); + render_sidebar_rotation_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) //BBS: GUI refactor: add uniform_scale from gizmo - render_sidebar_scale_hints(sidebar_field, uniform_scale); + render_sidebar_scale_hints(sidebar_field, uniform_scale, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "layer")) - render_sidebar_layers_hints(sidebar_field); + render_sidebar_layers_hints(sidebar_field, *shader); - glsafe(::glPopMatrix()); - if (!boost::starts_with(sidebar_field, "layer")) - shader->stop_using(); + shader->stop_using(); } bool Selection::requires_local_axes() const { - return (m_mode == Volume) && is_from_single_instance(); + return m_mode == Volume && is_from_single_instance(); } void Selection::cut_to_clipboard() @@ -1678,8 +1712,7 @@ void Selection::copy_to_clipboard() m_clipboard.reset(); - for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) - { + for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) { ModelObject* src_object = m_model->objects[object.first]; ModelObject* dst_object = m_clipboard.add_object(); dst_object->name = src_object->name; @@ -1692,26 +1725,22 @@ void Selection::copy_to_clipboard() dst_object->layer_height_profile.assign(src_object->layer_height_profile); dst_object->origin_translation = src_object->origin_translation; - for (int i : object.second) - { + for (int i : object.second) { dst_object->add_instance(*src_object->instances[i]); } - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { // Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance. const GLVolume* volume = (*m_volumes)[i]; - if ((volume->object_idx() == object.first) && (volume->instance_idx() == *object.second.begin())) - { + if (volume->object_idx() == object.first && volume->instance_idx() == *object.second.begin()) { int volume_idx = volume->volume_idx(); - if ((0 <= volume_idx) && (volume_idx < (int)src_object->volumes.size())) - { + if (0 <= volume_idx && volume_idx < (int)src_object->volumes.size()) { ModelVolume* src_volume = src_object->volumes[volume_idx]; ModelVolume* dst_volume = dst_object->add_volume(*src_volume); dst_volume->set_new_unique_id(); - } else { - assert(false); } + else + assert(false); } } } @@ -2131,182 +2160,234 @@ void Selection::do_remove_object(unsigned int object_idx) } } -void Selection::render_selected_volumes() const -{ - float color[3] = { 1.0f, 1.0f, 1.0f }; - render_bounding_box(get_bounding_box(), color); -} - -void Selection::render_synchronized_volumes() const +void Selection::render_synchronized_volumes() { if (m_mode == Instance) return; - float color[3] = { 1.0f, 1.0f, 0.0f }; - for (unsigned int i : m_list) { - const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); - int volume_idx = volume->volume_idx(); + const GLVolume& volume = *(*m_volumes)[i]; + int object_idx = volume.object_idx(); + int volume_idx = volume.volume_idx(); for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (i == j) continue; - const GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) + const GLVolume& v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx || v.volume_idx() != volume_idx) continue; - render_bounding_box(v->transformed_convex_hull_bounding_box(), color); + render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); } } } -void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) const +void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) { - if (color == nullptr) - return; + const BoundingBoxf3& curr_box = m_box.get_bounding_box(); + if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { + m_box.reset(); - Vec3f b_min = box.min.cast(); - Vec3f b_max = box.max.cast(); - Vec3f size = 0.2f * box.size().cast(); + const Vec3f b_min = box.min.cast(); + const Vec3f b_max = box.max.cast(); + const Vec3f size = 0.2f * box.size().cast(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(48); + init_data.reserve_indices(48); + + // vertices + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z())); + + // indices + for (unsigned short i = 0; i < 48; ++i) { + init_data.add_index(i); + } + + m_box.init_from(std::move(init_data)); + } glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glColor3fv(color)); glsafe(::glLineWidth(2.0f * m_scale_factor)); - ::glBegin(GL_LINES); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); - - glsafe(::glEnd()); + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + m_box.set_color(to_rgba(color)); + m_box.render(); + shader->stop_using(); } -static std::array get_color(Axis axis) +static ColorRGBA get_color(Axis axis) { - return { GLGizmoBase::AXES_COLOR[axis][0], - GLGizmoBase::AXES_COLOR[axis][1], - GLGizmoBase::AXES_COLOR[axis][2], - GLGizmoBase::AXES_COLOR[axis][3] }; -}; + return GLGizmoBase::AXES_COLOR[axis]; +} -void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); + if (boost::ends_with(sidebar_field, "x")) { - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - const_cast(&m_arrow)->set_color(-1, get_color(X)); + const Transform3d model_matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader.set_uniform("view_normal_matrix", view_normal_matrix); + m_arrow.set_color(get_color(X)); m_arrow.render(); } else if (boost::ends_with(sidebar_field, "y")) { - const_cast(&m_arrow)->set_color(-1, get_color(Y)); + shader.set_uniform("view_model_matrix", view_matrix * matrix); + shader.set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); + m_arrow.set_color(get_color(Y)); m_arrow.render(); } else if (boost::ends_with(sidebar_field, "z")) { - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - const_cast(&m_arrow)->set_color(-1, get_color(Z)); + const Transform3d model_matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX()); + shader.set_uniform("view_model_matrix", view_matrix * model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader.set_uniform("view_normal_matrix", view_normal_matrix); + m_arrow.set_color(get_color(Z)); m_arrow.render(); } } -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) { - auto render_sidebar_rotation_hint = [this]() { + auto render_sidebar_rotation_hint = [this](GLShaderProgram& shader, const Transform3d& view_matrix, const Transform3d& model_matrix) { + shader.set_uniform("view_model_matrix", view_matrix * model_matrix); + Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader.set_uniform("view_normal_matrix", view_normal_matrix); m_curved_arrow.render(); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); + const Transform3d matrix = model_matrix * Geometry::assemble_transform(Vec3d::Zero(), PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_matrix * matrix); + view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader.set_uniform("view_normal_matrix", view_normal_matrix); m_curved_arrow.render(); }; + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); + if (boost::ends_with(sidebar_field, "x")) { - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); - const_cast(&m_curved_arrow)->set_color(-1, get_color(X)); - render_sidebar_rotation_hint(); + m_curved_arrow.set_color(get_color(X)); + render_sidebar_rotation_hint(shader, view_matrix, matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY())); } else if (boost::ends_with(sidebar_field, "y")) { - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - const_cast(&m_curved_arrow)->set_color(-1, get_color(Y)); - render_sidebar_rotation_hint(); + m_curved_arrow.set_color(get_color(Y)); + render_sidebar_rotation_hint(shader, view_matrix, matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitX())); } else if (boost::ends_with(sidebar_field, "z")) { - const_cast(&m_curved_arrow)->set_color(-1, get_color(Z)); - render_sidebar_rotation_hint(); + m_curved_arrow.set_color(get_color(Z)); + render_sidebar_rotation_hint(shader, view_matrix, matrix); } } //BBS: GUI refactor: add gizmo uniform_scale -void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale) const +void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale, GLShaderProgram& shader, const Transform3d& matrix) { // BBS //bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); bool uniform_scale = requires_uniform_scale() || gizmo_uniform_scale; - auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) { - const_cast(&m_arrow)->set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); - GLShaderProgram* shader = wxGetApp().get_current_shader(); - if (shader != nullptr) - shader->set_uniform("emission_factor", 0.0f); - - glsafe(::glTranslated(0.0, 5.0, 0.0)); + auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& view_matrix, const Transform3d& model_matrix) { + m_arrow.set_color(uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); + Transform3d matrix = model_matrix * Geometry::assemble_transform(5.0 * Vec3d::UnitY()); + shader.set_uniform("view_model_matrix", view_matrix * matrix); + Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader.set_uniform("view_normal_matrix", view_normal_matrix); m_arrow.render(); - glsafe(::glTranslated(0.0, -10.0, 0.0)); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); + matrix = model_matrix * Geometry::assemble_transform(-5.0 * Vec3d::UnitY(), PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_matrix * matrix); + view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader.set_uniform("view_normal_matrix", view_normal_matrix); m_arrow.render(); }; + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); + if (boost::ends_with(sidebar_field, "x") || uniform_scale) { - glsafe(::glPushMatrix()); - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - render_sidebar_scale_hint(X); - glsafe(::glPopMatrix()); + render_sidebar_scale_hint(X, shader, view_matrix, matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ())); } if (boost::ends_with(sidebar_field, "y") || uniform_scale) { - glsafe(::glPushMatrix()); - render_sidebar_scale_hint(Y); - glsafe(::glPopMatrix()); + render_sidebar_scale_hint(Y, shader, view_matrix, matrix); } if (boost::ends_with(sidebar_field, "z") || uniform_scale) { - glsafe(::glPushMatrix()); - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - render_sidebar_scale_hint(Z); - glsafe(::glPopMatrix()); + render_sidebar_scale_hint(Z, shader, view_matrix, matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX())); } } -void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field, GLShaderProgram& shader) { - static const double Margin = 10.0; + static const float Margin = 10.0f; std::string field = sidebar_field; @@ -2315,7 +2396,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co if (pos == std::string::npos) return; - double max_z = string_to_double_decimal_point(field.substr(pos + 1)); + const float max_z = float(string_to_double_decimal_point(field.substr(pos + 1))); // extract min_z field = field.substr(0, pos); @@ -2323,7 +2404,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co if (pos == std::string::npos) return; - const double min_z = string_to_double_decimal_point(field.substr(pos + 1)); + const float min_z = float(string_to_double_decimal_point(field.substr(pos + 1))); // extract type field = field.substr(0, pos); @@ -2335,42 +2416,71 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co const BoundingBoxf3& box = get_bounding_box(); - const float min_x = box.min(0) - Margin; - const float max_x = box.max(0) + Margin; - const float min_y = box.min(1) - Margin; - const float max_y = box.max(1) + Margin; - // view dependend order of rendering to keep correct transparency - bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); + const bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); const float z1 = camera_on_top ? min_z : max_z; const float z2 = camera_on_top ? max_z : min_z; + const Vec3f p1 = { float(box.min.x()) - Margin, float(box.min.y()) - Margin, z1 }; + const Vec3f p2 = { float(box.max.x()) + Margin, float(box.max.y()) + Margin, z2 }; + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - ::glBegin(GL_QUADS); - if ((camera_on_top && type == 1) || (!camera_on_top && type == 2)) - ::glColor4f(0.0f, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f); - else - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, z1); - ::glVertex3f(max_x, min_y, z1); - ::glVertex3f(max_x, max_y, z1); - ::glVertex3f(min_x, max_y, z1); - glsafe(::glEnd()); + if (!m_planes.models[0].is_initialized() || !is_approx(m_planes.check_points[0], p1)) { + m_planes.check_points[0] = p1; + m_planes.models[0].reset(); - ::glBegin(GL_QUADS); - if ((camera_on_top && type == 2) || (!camera_on_top && type == 1)) - ::glColor4f(0.0f, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f); - else - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, z2); - ::glVertex3f(max_x, min_y, z2); - ::glVertex3f(max_x, max_y, z2); - ::glVertex3f(min_x, max_y, z2); - glsafe(::glEnd()); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(p1.x(), p1.y(), z1)); + init_data.add_vertex(Vec3f(p2.x(), p1.y(), z1)); + init_data.add_vertex(Vec3f(p2.x(), p2.y(), z1)); + init_data.add_vertex(Vec3f(p1.x(), p2.y(), z1)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_planes.models[0].init_from(std::move(init_data)); + } + + if (!m_planes.models[1].is_initialized() || !is_approx(m_planes.check_points[1], p2)) { + m_planes.check_points[1] = p2; + m_planes.models[1].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(p1.x(), p1.y(), z2)); + init_data.add_vertex(Vec3f(p2.x(), p1.y(), z2)); + init_data.add_vertex(Vec3f(p2.x(), p2.y(), z2)); + init_data.add_vertex(Vec3f(p1.x(), p2.y(), z2)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_planes.models[1].init_from(std::move(init_data)); + } + + const Camera& camera = wxGetApp().plater()->get_camera(); + shader.set_uniform("view_model_matrix", camera.get_view_matrix()); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); + + m_planes.models[0].set_color((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); + m_planes.models[0].render(); + m_planes.models[1].set_color((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); + m_planes.models[1].render(); glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index bfee49bd2b..e130cb18be 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -1,7 +1,12 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GUI_Selection_hpp_ #define slic3r_GUI_Selection_hpp_ #include "libslic3r/Geometry.hpp" +#include "GUI_Geometry.hpp" #include "GLModel.hpp" #include @@ -26,58 +31,6 @@ using ModelObjectPtrs = std::vector; namespace GUI { -class TransformationType -{ -public: - enum Enum { - // Transforming in a world coordinate system - World = 0, - // Transforming in a local coordinate system - Local = 1, - // Absolute transformations, allowed in local coordinate system only. - Absolute = 0, - // Relative transformations, allowed in both local and world coordinate system. - Relative = 2, - // For group selection, the transformation is performed as if the group made a single solid body. - Joint = 0, - // For group selection, the transformation is performed on each object independently. - Independent = 4, - - World_Relative_Joint = World | Relative | Joint, - World_Relative_Independent = World | Relative | Independent, - Local_Absolute_Joint = Local | Absolute | Joint, - Local_Absolute_Independent = Local | Absolute | Independent, - Local_Relative_Joint = Local | Relative | Joint, - Local_Relative_Independent = Local | Relative | Independent, - }; - - TransformationType() : m_value(World) {} - TransformationType(Enum value) : m_value(value) {} - TransformationType& operator=(Enum value) { m_value = value; return *this; } - - Enum operator()() const { return m_value; } - bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } - - void set_world() { this->remove(Local); } - void set_local() { this->add(Local); } - void set_absolute() { this->remove(Relative); } - void set_relative() { this->add(Relative); } - void set_joint() { this->remove(Independent); } - void set_independent() { this->add(Independent); } - - bool world() const { return !this->has(Local); } - bool local() const { return this->has(Local); } - bool absolute() const { return !this->has(Relative); } - bool relative() const { return this->has(Relative); } - bool joint() const { return !this->has(Independent); } - bool independent() const { return this->has(Independent); } - -private: - void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } - void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } - - Enum m_value; -}; class Selection { @@ -220,9 +173,15 @@ private: GLModel m_arrow; GLModel m_curved_arrow; + GLModel m_box; + struct Planes + { + std::array check_points{ Vec3f::Zero(), Vec3f::Zero() }; + std::array models; + }; + Planes m_planes; float m_scale_factor; - bool m_dragging; // BBS EMode m_volume_selection_mode{ Instance }; @@ -290,6 +249,8 @@ public: bool is_single_volume() const { return m_type == SingleVolume; } bool is_multiple_volume() const { return m_type == MultipleVolume; } bool is_any_volume() const { return is_single_volume() || is_multiple_volume(); } + bool is_any_connector() const; + bool is_any_cut_volume() const; bool is_mixed() const { return m_type == Mixed; } bool is_from_single_instance() const { return get_instance_idx() != -1; } bool is_from_single_object() const; @@ -301,6 +262,8 @@ public: bool contains_all_volumes(const std::vector& volume_idxs) const; // returns true if the selection contains at least one of the given indices bool contains_any_volume(const std::vector& volume_idxs) const; + // returns true if the selection contains any sinking volume + bool contains_sinking_volumes(bool ignore_modifiers = true) const; // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; @@ -316,6 +279,7 @@ public: const IndicesList& get_volume_idxs() const { return m_list; } const GLVolume* get_volume(unsigned int volume_idx) const; + const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); } const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } @@ -326,9 +290,7 @@ public: const BoundingBoxf3& get_unscaled_instance_bounding_box() const; const BoundingBoxf3& get_scaled_instance_bounding_box() const; - void start_dragging(); - void stop_dragging() { m_dragging = false; } - bool is_dragging() const { return m_dragging; } + void setup_cache(); void translate(const Vec3d& displacement, bool local = false); void move_to_center(const Vec3d& displacement, bool local = false); @@ -353,16 +315,16 @@ public: void erase(); - void render(float scale_factor = 1.0) const; -#if ENABLE_RENDER_SELECTION_CENTER - void render_center(bool gizmo_is_dragging) const; -#endif // ENABLE_RENDER_SELECTION_CENTER + void render(float scale_factor = 1.0); //BBS: GUI refactor: add uniform scale from gizmo - void render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale) const; + void render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale); +#if ENABLE_RENDER_SELECTION_CENTER + void render_center(bool gizmo_is_dragging); +#endif // ENABLE_RENDER_SELECTION_CENTER bool requires_local_axes() const; - void render_bounding_box(const BoundingBoxf3& box, float* color, float scale) { + void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color, float scale) { m_scale_factor = scale; render_bounding_box(box, color); } @@ -399,14 +361,13 @@ private: void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } - void render_selected_volumes() const; - void render_synchronized_volumes() const; - void render_bounding_box(const BoundingBoxf3& box, float* color) const; - void render_sidebar_position_hints(const std::string& sidebar_field) const; - void render_sidebar_rotation_hints(const std::string& sidebar_field) const; + void render_synchronized_volumes(); + void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color); + void render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix); + void render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix); //BBS: GUI refactor: add uniform_scale from gizmo - void render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale) const; - void render_sidebar_layers_hints(const std::string& sidebar_field) const; + void render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale, GLShaderProgram& shader, const Transform3d& matrix); + void render_sidebar_layers_hints(const std::string& sidebar_field, GLShaderProgram& shader); public: enum SyncRotationType { diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 5f192f3635..b93c00f767 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -8,6 +8,7 @@ #include "libslic3r/BlacklistedLibraryCheck.hpp" #include "libslic3r/Platform.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/Color.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Http.hpp" @@ -591,9 +592,8 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) wxColour bgr_clr = wxGetApp().get_window_default_clr(); SetBackgroundColour(bgr_clr); const auto text_clr = wxGetApp().get_label_clr_default(); - auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); - + auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); auto *topSizer = new wxBoxSizer(wxVERTICAL); auto *vsizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index ce7f01f140..c3ff06873d 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -15,6 +15,7 @@ #include "MainFrame.hpp" #include "wxExtensions.hpp" #include "../libslic3r/BlacklistedLibraryCheck.hpp" +#include "../libslic3r/Color.hpp" #include "format.hpp" #ifdef _WIN32 @@ -114,8 +115,8 @@ SysInfoDialog::SysInfoDialog() // main_info_text wxFont font = get_default_font(this); const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); const int fs = font.GetPointSize() - 1; int size[] = { static_cast(fs*1.5), static_cast(fs*1.4), static_cast(fs*1.3), fs, fs, fs, fs }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9a38c7409a..68a0990436 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1911,6 +1911,7 @@ void TabPrint::build() optgroup->append_single_option_line("max_travel_detour_distance"); optgroup->append_single_option_line("extra_perimeters_on_overhangs"); optgroup->append_single_option_line("overhang_reverse"); + optgroup->append_single_option_line("overhang_reverse_internal_only"); optgroup->append_single_option_line("overhang_reverse_threshold"); page = add_options_page(L("Strength"), "empty"); @@ -2574,8 +2575,8 @@ bool Tab::validate_custom_gcode(const wxString& title, const std::string& gcode) return !invalid; } -static void validate_custom_gcode_cb(Tab* tab, ConfigOptionsGroupShp opt_group, const t_config_option_key& opt_key, const boost::any& value) { - tab->validate_custom_gcodes_was_shown = !Tab::validate_custom_gcode(opt_group->title, boost::any_cast(value)); +static void validate_custom_gcode_cb(Tab* tab, const wxString& title, const t_config_option_key& opt_key, const boost::any& value) { + tab->validate_custom_gcodes_was_shown = !Tab::validate_custom_gcode(title, boost::any_cast(value)); tab->update_dirty(); tab->on_value_change(opt_key, value); } @@ -2592,18 +2593,19 @@ void TabFilament::add_filament_overrides_page() //BBS line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); - line.near_label_widget = [this, optgroup, opt_key, opt_index](wxWindow* parent) { + line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) { wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); - check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_key, opt_index](wxCommandEvent& evt) { + check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) { const bool is_checked = evt.IsChecked(); - Field* field = optgroup->get_fieldc(opt_key, opt_index); - if (field != nullptr) { - field->toggle(is_checked); - if (is_checked) - field->set_last_meaningful_value(); - else - field->set_na_value(); + if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) { + if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) { + field->toggle(is_checked); + if (is_checked) + field->set_last_meaningful_value(); + else + field->set_na_value(); + } } }, check_box->GetId()); @@ -2760,7 +2762,7 @@ void TabFilament::build() line.append_option(optgroup->get_option("textured_plate_temp")); optgroup->append_line(line); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { DynamicPrintConfig& filament_config = wxGetApp().preset_bundle->filaments.get_edited_preset().config; @@ -2855,8 +2857,8 @@ void TabFilament::build() page = add_options_page(L("Advanced"), "advanced"); optgroup = page->new_optgroup(L("Filament start G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("filament_start_gcode"); option.opt.full_width = true; @@ -2865,8 +2867,8 @@ void TabFilament::build() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Filament end G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("filament_end_gcode"); option.opt.full_width = true; @@ -3172,8 +3174,8 @@ void TabPrinter::build_fff() const int notes_field_height = 25; // 250 page = add_options_page(L("Machine gcode"), "cog"); optgroup = page->new_optgroup(L("Machine start G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("machine_start_gcode"); option.opt.full_width = true; @@ -3182,8 +3184,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Machine end G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("machine_end_gcode"); option.opt.full_width = true; @@ -3192,8 +3194,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Before layer change G-code"),"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("before_layer_change_gcode"); option.opt.full_width = true; @@ -3202,8 +3204,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Layer change G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("layer_change_gcode"); option.opt.full_width = true; @@ -3212,8 +3214,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Time lapse G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("time_lapse_gcode"); option.opt.full_width = true; @@ -3222,8 +3224,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Change filament G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("change_filament_gcode"); option.opt.full_width = true; @@ -3232,8 +3234,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Change extrusion role G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key &opt_key, const boost::any &value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("change_extrusion_role_gcode"); @@ -3243,8 +3245,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Pause G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("machine_pause_gcode"); option.opt.is_code = true; @@ -3252,8 +3254,8 @@ void TabPrinter::build_fff() optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Template Custom G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); }; option = optgroup->get_option("template_custom_gcode"); option.opt.is_code = true; @@ -3481,7 +3483,7 @@ if (is_marlin_flavor) auto optgroup = page->new_optgroup(L("Single extruder multimaterial setup")); optgroup->append_single_option_line("single_extruder_multi_material", "semm"); // Orca: we only support Single Extruder Multi Material, so it's always enabled - // optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { + // optgroup->m_on_change = [this](const t_config_option_key &opt_key, const boost::any &value) { // wxTheApp->CallAfter([this, opt_key, value]() { // if (opt_key == "single_extruder_multi_material") { // build_unregular_pages(); diff --git a/src/slic3r/GUI/TickCode.cpp b/src/slic3r/GUI/TickCode.cpp index 158afe7a1f..267752c544 100644 --- a/src/slic3r/GUI/TickCode.cpp +++ b/src/slic3r/GUI/TickCode.cpp @@ -4,28 +4,39 @@ namespace Slic3r { namespace GUI { std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder) { + auto opposite_one_color = [](const std::string& color) { + ColorRGB rgb; + decode_color(color, rgb); + return encode_color(opposite(rgb)); + }; + auto opposite_two_colors = [](const std::string& a, const std::string& b) { + ColorRGB rgb1; decode_color(a, rgb1); + ColorRGB rgb2; decode_color(b, rgb2); + return encode_color(opposite(rgb1, rgb2)); + }; + if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) { #if 1 - if (ticks.empty()) return color_generator.get_opposite_color((*m_colors)[0]); + if (ticks.empty()) return opposite_one_color((*m_colors)[0]); auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick); if (before_tick_it == ticks.end()) { while (before_tick_it != ticks.begin()) if (--before_tick_it; before_tick_it->type == ColorChange) break; - if (before_tick_it->type == ColorChange) return color_generator.get_opposite_color(before_tick_it->color); - return color_generator.get_opposite_color((*m_colors)[0]); + if (before_tick_it->type == ColorChange) return opposite_one_color(before_tick_it->color); + return opposite_one_color((*m_colors)[0]); } if (before_tick_it == ticks.begin()) { const std::string &frst_color = (*m_colors)[0]; - if (before_tick_it->type == ColorChange) return color_generator.get_opposite_color(frst_color, before_tick_it->color); + if (before_tick_it->type == ColorChange) return opposite_two_colors(frst_color, before_tick_it->color); auto next_tick_it = before_tick_it; while (next_tick_it != ticks.end()) if (++next_tick_it; next_tick_it->type == ColorChange) break; - if (next_tick_it->type == ColorChange) return color_generator.get_opposite_color(frst_color, next_tick_it->color); + if (next_tick_it->type == ColorChange) return opposite_two_colors(frst_color, next_tick_it->color); - return color_generator.get_opposite_color(frst_color); + return opposite_one_color(frst_color); } std::string frst_color = ""; @@ -44,12 +55,12 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int if (--before_tick_it; before_tick_it->type == ColorChange) break; if (before_tick_it->type == ColorChange) { - if (frst_color.empty()) return color_generator.get_opposite_color(before_tick_it->color); - return color_generator.get_opposite_color(before_tick_it->color, frst_color); + if (frst_color.empty()) return opposite_one_color(before_tick_it->color); + return opposite_two_colors(before_tick_it->color, frst_color); } - if (frst_color.empty()) return color_generator.get_opposite_color((*m_colors)[0]); - return color_generator.get_opposite_color((*m_colors)[0], frst_color); + if (frst_color.empty()) return opposite_one_color((*m_colors)[0]); + return opposite_two_colors((*m_colors)[0], frst_color); #else const std::vector &colors = ColorPrintColors::get(); if (ticks.empty()) return colors[0]; diff --git a/src/slic3r/GUI/TickCode.hpp b/src/slic3r/GUI/TickCode.hpp index 8616d7565a..14785dee55 100644 --- a/src/slic3r/GUI/TickCode.hpp +++ b/src/slic3r/GUI/TickCode.hpp @@ -2,7 +2,7 @@ #define slic3r_GUI_TickCode_hpp_ #include "libslic3r/CustomGCode.hpp" -#include "IMSlider_Utils.hpp" +#include "libslic3r/Color.hpp" #include namespace Slic3r { @@ -29,7 +29,6 @@ class TickCodeInfo bool m_use_default_colors = false; std::vector* m_colors{ nullptr };// reference to IMSlider::m_extruder_colors - ColorGenerator color_generator; std::string get_color_for_tick(TickCode tick, Type type, const int extruder); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index a5868b377b..a04152f004 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -11,6 +11,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" #include "format.hpp" #include "GUI_App.hpp" #include "Plater.hpp" @@ -60,8 +61,7 @@ static std::string get_icon_name(Preset::Type type, PrinterTechnology pt) { static std::string def_text_color() { wxColour def_colour = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), def_colour.Red(), def_colour.Green(), def_colour.Blue()); - return clr_str.ToStdString(); + return encode_color(ColorRGB(def_colour.Red(), def_colour.Green(), def_colour.Blue())); } static std::string grey = "#808080"; static std::string orange = "#ed6b21"; @@ -126,8 +126,8 @@ wxBitmap ModelNode::get_bitmap(const wxString& color) const int icon_height = lround(1.6 * em); BitmapCache bmp_cache; - unsigned char rgb[3]; - BitmapCache::parse_color(into_u8(color), rgb); + ColorRGB rgb; + decode_color(into_u8(color), rgb); // there is no need to scale created solid bitmap #ifndef __linux__ return bmp_cache.mksolid(icon_width, icon_height, rgb, true); diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 4646afc897..55d47c1b84 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -7,6 +7,7 @@ #include "I18N.hpp" #include "GUI_App.hpp" #include "MsgDialog.hpp" +#include "libslic3r/Color.hpp" #include "Widgets/Button.hpp" #include "slic3r/Utils/ColorSpaceConvert.hpp" #include "MainFrame.hpp" @@ -407,9 +408,9 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con m_number_of_extruders = (int)(sqrt(matrix.size())+0.001); for (const std::string& color : extruder_colours) { - //unsigned char rgb[3]; - //Slic3r::GUI::BitmapCache::parse_color(color, rgb); - m_colours.push_back(wxColor(color)); + Slic3r::ColorRGB rgb; + Slic3r::decode_color(color, rgb); + m_colours.push_back(wxColor(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar())); } // Create two switched panels with their own sizers diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index 6561bedfe7..8186f4dfc2 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -4,6 +4,7 @@ #include "../GUI/DeviceManager.hpp" #include "../GUI/Jobs/ProgressIndicator.hpp" #include "../GUI/PartPlate.hpp" +#include "libslic3r/CutUtils.hpp" #include "libslic3r/Model.hpp" @@ -133,7 +134,7 @@ bool CalibUtils::validate_input_flow_ratio(wxString flow_ratio, float* output_va return true; } -static void cut_model(Model &model, std::array plane_points, ModelObjectCutAttributes attributes) +static void cut_model(Model &model, double z, ModelObjectCutAttributes attributes) { size_t obj_idx = 0; size_t instance_idx = 0; @@ -142,7 +143,9 @@ static void cut_model(Model &model, std::array plane_points, ModelObje auto* object = model.objects[0]; - const auto new_objects = object->cut(instance_idx, plane_points, attributes); + const Vec3d instance_offset = object->instances[instance_idx]->get_offset(); + Cut cut(object, instance_idx, Geometry::translation_transform(z * Vec3d::UnitZ() - instance_offset), attributes); + const auto new_objects = cut.perform_with_plane(); model.delete_object(obj_idx); for (ModelObject *model_object : new_objects) { @@ -173,16 +176,6 @@ static void read_model_from_file(const std::string& input_file, Model& model) object->ensure_on_bed(); } -std::array get_cut_plane_points(const BoundingBoxf3 &bbox, const double &cut_height) -{ - std::array plane_pts; - plane_pts[0] = Vec3d(bbox.min(0), bbox.min(1), cut_height); - plane_pts[1] = Vec3d(bbox.max(0), bbox.min(1), cut_height); - plane_pts[2] = Vec3d(bbox.max(0), bbox.max(1), cut_height); - plane_pts[3] = Vec3d(bbox.min(0), bbox.max(1), cut_height); - return plane_pts; -} - void CalibUtils::calib_PA(const X1CCalibInfos& calib_infos, int mode, wxString& error_message) { DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); @@ -564,12 +557,7 @@ void CalibUtils::calib_temptue(const CalibInfo &calib_info, wxString &error_mess // add EPSILON offset to avoid cutting at the exact location where the flat surface is auto new_height = block_count * 10.0 + EPSILON; if (new_height < obj_bb.size().z()) { - std::array plane_pts; - plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), new_height); - plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), new_height); - plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), new_height); - plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), new_height); - cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_model(model, new_height, ModelObjectCutAttribute::KeepLower); } } @@ -579,12 +567,7 @@ void CalibUtils::calib_temptue(const CalibInfo &calib_info, wxString &error_mess if (block_count > 0) { auto new_height = block_count * 10.0 + EPSILON; if (new_height < obj_bb.size().z()) { - std::array plane_pts; - plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), new_height); - plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), new_height); - plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), new_height); - plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), new_height); - cut_model(model, plane_pts, ModelObjectCutAttribute::KeepUpper); + cut_model(model, new_height, ModelObjectCutAttribute::KeepUpper); } } @@ -670,12 +653,7 @@ void CalibUtils::calib_max_vol_speed(const CalibInfo &calib_info, wxString &erro auto obj_bb = obj->bounding_box(); double height = (params.end - params.start + 1) / params.step; if (height < obj_bb.size().z()) { - std::array plane_pts; - plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), height); - plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), height); - plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), height); - plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), height); - cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_model(model, height, ModelObjectCutAttribute::KeepLower); } auto new_params = params; @@ -731,12 +709,7 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message) auto obj_bb = model.objects[0]->bounding_box(); auto height = 5 * ((params.end - params.start) / params.step + 1); if (height < obj_bb.size().z()) { - std::array plane_pts; - plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), height); - plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), height); - plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), height); - plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), height); - cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_model(model, height, ModelObjectCutAttribute::KeepLower); } else { error_message = _L("The start, end or step is not valid value."); @@ -790,8 +763,7 @@ void CalibUtils::calib_retraction(const CalibInfo &calib_info, wxString &error_m auto obj_bb = obj->bounding_box(); auto height = 1.0 + 0.4 + ((params.end - params.start)) / params.step; if (height < obj_bb.size().z()) { - std::array plane_pts = get_cut_plane_points(obj_bb, height); - cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower); + cut_model(model, height, ModelObjectCutAttribute::KeepLower); } DynamicPrintConfig full_config; @@ -909,9 +881,9 @@ void CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f //draw thumbnails { GLVolumeCollection glvolume_collection; - std::vector> colors_out(1); + std::vector colors_out(1); unsigned char rgb_color[4] = {255, 255, 255, 255}; - std::array new_color {1.0f, 1.0f, 1.0f, 1.0f}; + ColorRGBA new_color {1.0f, 1.0f, 1.0f, 1.0f}; colors_out.push_back(new_color); ThumbnailData* thumbnail_data = &plate_data_list[0]->plate_thumbnail; @@ -926,8 +898,8 @@ void CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f const ModelVolume &model_volume = *model_object.volumes[volume_idx]; for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { const ModelInstance &model_instance = *model_object.instances[instance_idx]; - glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, "volume", true, false, true); - glvolume_collection.volumes.back()->set_render_color( new_color[0], new_color[1], new_color[2], new_color[3]); + glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, false, true); + glvolume_collection.volumes.back()->set_render_color(new_color); glvolume_collection.volumes.back()->set_color(new_color); //glvolume_collection.volumes.back()->printable = model_instance.printable; } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 56a5172ab3..eaf90c7349 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -21,6 +21,7 @@ #include #include +#include "slic3r/GUI/3DScene.hpp" #include #ifndef NDEBUG