// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2023 Werner Mayer * * * * This file is part of FreeCAD. * * * * FreeCAD is free software: you can redistribute it and/or modify it * * under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 2.1 of the * * License, or (at your option) any later version. * * * * FreeCAD is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with FreeCAD. If not, see * * . * * * **************************************************************************/ #include #include #include #include #include #include #include #include #include "DlgSettingsLightSources.h" #include "ui_DlgSettingsLightSources.h" #include #include #include #include #include using namespace Gui::Dialog; /* TRANSLATOR Gui::Dialog::DlgSettingsLightSources */ DlgSettingsLightSources::DlgSettingsLightSources(QWidget* parent) : PreferencePage(parent) , ui(new Ui_DlgSettingsLightSources) { ui->setupUi(this); view = ui->viewer; configureViewer(); const auto connectLightEvents = [this]( QuantitySpinBox* horizontalAngleSpinBox, QuantitySpinBox* verticalAngleSpinBox, QSpinBox* intensitySpinBox, ColorButton* colorButton, QCheckBox* enabledCheckbox, auto updateLightFunction ) { connect( horizontalAngleSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, updateLightFunction ); connect( verticalAngleSpinBox, qOverload(&QuantitySpinBox::valueChanged), this, updateLightFunction ); connect(intensitySpinBox, qOverload(&QSpinBox::valueChanged), this, updateLightFunction); connect(colorButton, &ColorButton::changed, this, updateLightFunction); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(enabledCheckbox, &QCheckBox::checkStateChanged, this, updateLightFunction); #else connect(enabledCheckbox, &QCheckBox::stateChanged, this, updateLightFunction); #endif }; const auto updateLight = [this]( SoDirectionalLight* light, QuantitySpinBox* horizontalAngleSpinBox, QuantitySpinBox* verticalAngleSpinBox, QSpinBox* intensitySpinBox, ColorButton* colorButton, QCheckBox* enabledCheckbox, std::function setLightEnabled ) { light->color = Base::convertTo(colorButton->color()); light->intensity = Base::fromPercent(intensitySpinBox->value()); light->direction = Base::convertTo(azimuthElevationToDirection( horizontalAngleSpinBox->rawValue(), verticalAngleSpinBox->rawValue() )); setLightEnabled(enabledCheckbox->isChecked()); }; const auto updateHeadLight = [this, updateLight] { updateLight( view->getHeadlight(), ui->mainLightHorizontalAngle, ui->mainLightVerticalAngle, ui->mainLightIntensitySpinBox, ui->mainLightColor, ui->mainLightEnable, [this](bool enabled) { view->setHeadlightEnabled(enabled); } ); }; const auto updateFillLight = [this, updateLight] { updateLight( view->getFillLight(), ui->fillLightHorizontalAngle, ui->fillLightVerticalAngle, ui->fillLightIntensitySpinBox, ui->fillLightColor, ui->fillLightEnable, [this](bool enabled) { view->setFillLightEnabled(enabled); } ); }; const auto updateBackLight = [this, updateLight] { updateLight( view->getBacklight(), ui->backLightHorizontalAngle, ui->backLightVerticalAngle, ui->backLightIntensitySpinBox, ui->backLightColor, ui->backLightEnable, [this](bool enabled) { view->setBacklightEnabled(enabled); } ); }; connectLightEvents( ui->mainLightHorizontalAngle, ui->mainLightVerticalAngle, ui->mainLightIntensitySpinBox, ui->mainLightColor, ui->mainLightEnable, updateHeadLight ); connectLightEvents( ui->backLightHorizontalAngle, ui->backLightVerticalAngle, ui->backLightIntensitySpinBox, ui->backLightColor, ui->backLightEnable, updateBackLight ); connectLightEvents( ui->fillLightHorizontalAngle, ui->fillLightVerticalAngle, ui->fillLightIntensitySpinBox, ui->fillLightColor, ui->fillLightEnable, updateFillLight ); const auto updateAmbientLight = [this] { view->getEnvironment()->ambientColor = Base::convertTo(ui->ambientLightColor->color()); view->getEnvironment()->ambientIntensity = Base::fromPercent( ui->ambientLightIntensitySpinBox->value() ); }; connect( ui->ambientLightIntensitySpinBox, qOverload(&QSpinBox::valueChanged), this, updateAmbientLight ); connect(ui->ambientLightColor, &ColorButton::changed, this, updateAmbientLight); connect(ui->zoomInButton, &QToolButton::clicked, this, &DlgSettingsLightSources::zoomIn); connect(ui->zoomOutButton, &QToolButton::clicked, this, &DlgSettingsLightSources::zoomOut); DlgSettingsLightSources::loadSettings(); } static inline SoMaterial* createMaterial(void) { const QColor ambientColor {0xff333333}, diffuseColor {0xffd2d2ff}, emissiveColor {0xff000000}, specularColor {0xffcccccc}; auto material = new SoMaterial(); material->ambientColor.setValue(Base::convertTo(ambientColor)); material->diffuseColor.setValue(Base::convertTo(diffuseColor)); material->emissiveColor.setValue(Base::convertTo(emissiveColor)); material->specularColor.setValue(Base::convertTo(specularColor)); material->shininess = 0.9f; return material; } static inline SoSphere* createSphere(void) { auto sphere = new SoSphere(); sphere->radius = 3; return sphere; } static inline SoComplexity* createGoodComplexity() { auto complexity = new SoComplexity(); complexity->value = 1.0; return complexity; } void DlgSettingsLightSources::configureViewer() { const SbVec3f defaultViewDirection {0.0f, 1.0f, 0.3f}; View3DSettings(hGrpView, view).applySettings(); view->setRedirectToSceneGraph(true); view->setViewing(true); view->setPopupMenuEnabled(false); view->setEnabledNaviCube(false); const auto root = static_cast(view->getSceneGraph()); root->addChild(createGoodComplexity()); root->addChild(createMaterial()); root->addChild(createSphere()); const auto callback = new SoEventCallback(); root->addChild(callback); callback->addEventCallback( SoEvent::getClassTypeId(), []([[maybe_unused]] void* ud, SoEventCallback* cb) { cb->setHandled(); } ); view->setCameraType(SoOrthographicCamera::getClassTypeId()); view->setViewDirection(defaultViewDirection); view->viewAll(); camera = dynamic_cast(view->getCamera()); const float cameraHeight = camera->height.getValue() * 2.0f; camera->height = cameraHeight; zoomStep = cameraHeight / 14.0f; } Base::Vector3d DlgSettingsLightSources::azimuthElevationToDirection(double azimuth, double elevation) { azimuth = Base::toRadians(azimuth); elevation = Base::toRadians(elevation); auto direction = Base::Vector3d { std::sin(azimuth) * std::cos(elevation), std::cos(azimuth) * std::cos(elevation), std::sin(elevation) }; direction.Normalize(); return direction; } std::pair DlgSettingsLightSources::directionToAzimuthElevation(Base::Vector3d direction) { const auto azimuth = std::atan2(direction[0], direction[1]); const auto elevation = std::atan2( direction[2], std::sqrt(direction[1] * direction[1] + direction[0] * direction[0]) ); return {Base::toDegrees(azimuth), Base::toDegrees(elevation)}; } void DlgSettingsLightSources::saveSettings() { for (const auto& widget : findChildren()) { if (const auto pref = dynamic_cast(widget)) { pref->onSave(); } } const auto saveAngles = [this]( QuantitySpinBox* horizontalAngleSpinBox, QuantitySpinBox* verticalAngleSpinBox, const char* parameter ) { try { const auto direction = azimuthElevationToDirection( horizontalAngleSpinBox->rawValue(), verticalAngleSpinBox->rawValue() ); hGrp->SetASCII(parameter, Base::vectorToString(Base::convertTo(direction))); } catch (...) { } }; saveAngles(ui->mainLightHorizontalAngle, ui->mainLightVerticalAngle, "HeadlightDirection"); saveAngles(ui->backLightHorizontalAngle, ui->backLightVerticalAngle, "BacklightDirection"); saveAngles(ui->fillLightHorizontalAngle, ui->fillLightVerticalAngle, "FillLightDirection"); } void DlgSettingsLightSources::loadSettings() { for (const auto& widget : findChildren()) { if (const auto pref = dynamic_cast(widget)) { pref->onRestore(); } } const auto loadAngles = [this]( QuantitySpinBox* horizontalAngleSpinBox, QuantitySpinBox* verticalAngleSpinBox, const char* parameter ) { try { const auto direction = Base::stringToVector(hGrp->GetASCII(parameter)); const auto [azimuth, elevation] = directionToAzimuthElevation( Base::convertTo(direction) ); horizontalAngleSpinBox->setValue(azimuth); verticalAngleSpinBox->setValue(elevation); } catch (...) { } }; loadAngles(ui->mainLightHorizontalAngle, ui->mainLightVerticalAngle, "HeadlightDirection"); loadAngles(ui->backLightHorizontalAngle, ui->backLightVerticalAngle, "BacklightDirection"); loadAngles(ui->fillLightHorizontalAngle, ui->fillLightVerticalAngle, "FillLightDirection"); } void DlgSettingsLightSources::resetSettingsToDefaults() { PreferencePage::resetSettingsToDefaults(); hGrp->RemoveASCII("HeadlightDirection"); hGrp->RemoveASCII("BacklightDirection"); hGrp->RemoveASCII("FillLightDirection"); loadSettings(); configureViewer(); } void DlgSettingsLightSources::zoomIn() const { if (camera == nullptr) { return; } camera->height = camera->height.getValue() - zoomStep; } void DlgSettingsLightSources::zoomOut() const { if (camera == nullptr) { return; } camera->height = camera->height.getValue() + zoomStep; } void DlgSettingsLightSources::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } PreferencePage::changeEvent(event); } #include "moc_DlgSettingsLightSources.cpp"