/*************************************************************************** * Copyright (c) 2014 Joe Dowsett * * Copyright (c) 2014 Luke Parry * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #endif // #ifndef _PreComp_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ViewProviderProjGroup.h" #include "ViewProviderProjGroupItem.h" #include "ViewProviderPage.h" #include "TaskProjGroup.h" #include using namespace Gui; using namespace TechDraw; using namespace TechDrawGui; TaskProjGroup::TaskProjGroup(TechDraw::DrawProjGroup* featView, bool mode) : ui(new Ui_TaskProjGroup), multiView(featView), m_createMode(mode) { ui->setupUi(this); blockUpdate = true; ui->projection->setCurrentIndex(multiView->ProjectionType.getValue()); setFractionalScale(multiView->getScale()); ui->cmbScaleType->setCurrentIndex(multiView->ScaleType.getValue()); //Allow or prevent scale changing initially if (multiView->ScaleType.isValue("Custom")) { ui->sbScaleNum->setEnabled(true); ui->sbScaleDen->setEnabled(true); } else { ui->sbScaleNum->setEnabled(false); ui->sbScaleDen->setEnabled(false); } // Initially toggle view checkboxes if needed setupViewCheckboxes(true); blockUpdate = false; // Rotation buttons // Note we don't do the custom one here, as it's handled by [a different function that's held up in customs] connect(ui->butTopRotate, SIGNAL(clicked()), this, SLOT(rotateButtonClicked(void))); connect(ui->butCWRotate, SIGNAL(clicked()), this, SLOT(rotateButtonClicked(void))); connect(ui->butRightRotate, SIGNAL(clicked()), this, SLOT(rotateButtonClicked(void))); connect(ui->butDownRotate, SIGNAL(clicked()), this, SLOT(rotateButtonClicked(void))); connect(ui->butLeftRotate, SIGNAL(clicked()), this, SLOT(rotateButtonClicked(void))); connect(ui->butCCWRotate, SIGNAL(clicked()), this, SLOT(rotateButtonClicked(void))); //3D button connect(ui->but3D, SIGNAL(clicked()), this, SLOT(on3DClicked(void))); //Reset button connect(ui->butReset, SIGNAL(clicked()), this, SLOT(onResetClicked(void))); // Slot for Scale Type connect(ui->cmbScaleType, SIGNAL(currentIndexChanged(int)), this, SLOT(scaleTypeChanged(int))); connect(ui->sbScaleNum, SIGNAL(valueChanged(int)), this, SLOT(scaleManuallyChanged(int))); connect(ui->sbScaleDen, SIGNAL(valueChanged(int)), this, SLOT(scaleManuallyChanged(int))); // Slot for Projection Type (layout) connect(ui->projection, SIGNAL(currentIndexChanged(int)), this, SLOT(projectionTypeChanged(int))); m_page = multiView->findParentPage(); Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_page->getDocument()); Gui::ViewProvider* vp = activeGui->getViewProvider(m_page); ViewProviderPage* dvp = static_cast(vp); m_mdi = dvp->getMDIViewPage(); setUiPrimary(); } TaskProjGroup::~TaskProjGroup() { delete ui; } void TaskProjGroup::viewToggled(bool toggle) { bool changed = false; // Obtain name of checkbox QString viewName = sender()->objectName(); int index = viewName.mid(7).toInt(); const char *viewNameCStr = viewChkIndexToCStr(index); if ( toggle && !multiView->hasProjection( viewNameCStr ) ) { (void) multiView->addProjection( viewNameCStr ); changed = true; } else if ( !toggle && multiView->hasProjection( viewNameCStr ) ) { multiView->removeProjection( viewNameCStr ); changed = true; } if (changed) { if (multiView->ScaleType.isValue("Automatic")) { double scale = multiView->getScale(); setFractionalScale(scale); } } } void TaskProjGroup::rotateButtonClicked(void) { if ( multiView && ui ) { const QObject *clicked = sender(); if ( clicked == ui->butTopRotate ) { //change Front View Dir by 90 multiView->rotateUp(); } else if ( clicked == ui->butDownRotate) { multiView->rotateDown(); } else if ( clicked == ui->butRightRotate) { multiView->rotateRight(); } else if ( clicked == ui->butLeftRotate) { multiView->rotateLeft(); } else if ( clicked == ui->butCWRotate ) { //doesn't change Anchor view dir. changes projType of secondaries, not dir multiView->spinCW(); } else if ( clicked == ui->butCCWRotate) { multiView->spinCCW(); } setUiPrimary(); Gui::Command::updateActive(); } } void TaskProjGroup::on3DClicked(void) { Base::Console().Warning("TaskProjGroup - this function is temporarily unavailable\n"); //TODO: how to set the DPG.Cube (or a brand new replacement Cube) to a specific orientation // {10x(viewDirection + RotationVector)} given only the // viewDirection + upDirection(!= RotationVector) of the front view? // need to find the sequence of rotations Left/Right, Up/Down, CW/CCW // from current orientation to desired orientation. // std::pair dir3D = get3DViewDir(); // Base::Vector3d dir = dir3D.first; // dir = DrawUtil::closestBasis(dir); // Base::Vector3d up = dir3D.second; // up = DrawUtil::closestBasis(up); // TechDraw::DrawProjGroupItem* front = multiView->getProjItem("Front"); // if (front) { //why "if front"??? // multiView->setTable(dir,up); // setUiPrimary(); // Gui::Command::updateActive(); // } } void TaskProjGroup::onResetClicked(void) { TechDraw::DrawProjGroupItem* front = multiView->getProjItem("Front"); if (front) { multiView->resetCube(); setUiPrimary(); Gui::Command::updateActive(); } } void TaskProjGroup::projectionTypeChanged(int index) { if(blockUpdate) return; if(index == 0) { //layout per Page (Document) Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.ProjectionType = '%s'", multiView->getNameInDocument(), "Default"); } else if(index == 1) { // First Angle layout Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.ProjectionType = '%s'", multiView->getNameInDocument(), "First Angle"); } else if(index == 2) { // Third Angle layout Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.ProjectionType = '%s'", multiView->getNameInDocument(), "Third Angle"); } else { Base::Console().Log("Error - TaskProjGroup::projectionTypeChanged - unknown projection layout: %d\n", index); return; } // Update checkboxes so checked state matches the drawing setupViewCheckboxes(); } void TaskProjGroup::scaleTypeChanged(int index) { if(blockUpdate) return; //defaults to prevent scale changing ui->sbScaleNum->setEnabled(false); ui->sbScaleDen->setEnabled(false); if(index == 0) { // Document Scale Type Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.ScaleType = '%s'", multiView->getNameInDocument() , "Page"); } else if(index == 1) { // Automatic Scale Type Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.ScaleType = '%s'", multiView->getNameInDocument() , "Automatic"); } else if(index == 2) { // Custom Scale Type Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.ScaleType = '%s'", multiView->getNameInDocument() , "Custom"); ui->sbScaleNum->setEnabled(true); ui->sbScaleDen->setEnabled(true); int a = ui->sbScaleNum->value(); int b = ui->sbScaleDen->value(); double scale = (double) a / (double) b; Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.Scale = %f", multiView->getNameInDocument() , scale); } else { Base::Console().Log("Error - TaskProjGroup::scaleTypeChanged - unknown scale type: %d\n",index); return; } multiView->recomputeFeature(); Gui::Command::updateActive(); } std::pair TaskProjGroup::nearestFraction(const double val, const long int maxDenom) const { /* ** find rational approximation to given real number ** David Eppstein / UC Irvine / 8 Aug 1993 ** ** With corrections from Arno Formella, May 2008 ** and additional fiddles by WF 2017 ** usage: a.out r d ** r is real number to approx ** d is the maximum denominator allowed ** ** based on the theory of continued fractions ** if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...))) ** then best approximation is found by truncating this series ** (with some adjustments in the last term). ** ** Note the fraction can be recovered as the first column of the matrix ** ( a1 1 ) ( a2 1 ) ( a3 1 ) ... ** ( 1 0 ) ( 1 0 ) ( 1 0 ) ** Instead of keeping the sequence of continued fraction terms, ** we just keep the last partial product of these matrices. */ std::pair result; long m[2][2]; long maxden = maxDenom; long ai; double x = val; double startx = x; /* initialize matrix */ m[0][0] = m[1][1] = 1; m[0][1] = m[1][0] = 0; /* loop finding terms until denom gets too big */ while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden) { long t; t = m[0][0] * ai + m[0][1]; m[0][1] = m[0][0]; m[0][0] = t; t = m[1][0] * ai + m[1][1]; m[1][1] = m[1][0]; m[1][0] = t; if(x == (double) ai) break; // AF: division by zero x = 1/(x - (double) ai); if(x > (double) std::numeric_limits::max()) break; // AF: representation failure } /* now remaining x is between 0 and 1/ai */ /* approx as either 0 or 1/m where m is max that will fit in maxden */ /* first try zero */ double error1 = startx - ((double) m[0][0] / (double) m[1][0]); int n1 = m[0][0]; int d1 = m[1][0]; /* now try other possibility */ ai = (maxden - m[1][1]) / m[1][0]; m[0][0] = m[0][0] * ai + m[0][1]; m[1][0] = m[1][0] * ai + m[1][1]; double error2 = startx - ((double) m[0][0] / (double) m[1][0]); int n2 = m[0][0]; int d2 = m[1][0]; if (std::fabs(error1) <= std::fabs(error2)) { result.first = n1; result.second = d1; } else { result.first = n2; result.second = d2; } return result; } void TaskProjGroup::updateTask() { // Update the scale type blockUpdate = true; ui->cmbScaleType->setCurrentIndex(multiView->ScaleType.getValue()); // Update the scale value setFractionalScale(multiView->getScale()); blockUpdate = false; } void TaskProjGroup::setFractionalScale(double newScale) { blockUpdate = true; std::pair fraction = nearestFraction(newScale); ui->sbScaleNum->setValue(fraction.first); ui->sbScaleDen->setValue(fraction.second); blockUpdate = false; } void TaskProjGroup::scaleManuallyChanged(int i) { Q_UNUSED(i); if(blockUpdate) return; if (!multiView->ScaleType.isValue("Custom")) { //ignore if not custom! return; } int a = ui->sbScaleNum->value(); int b = ui->sbScaleDen->value(); double scale = (double) a / (double) b; Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.Scale = %f", multiView->getNameInDocument() , scale); multiView->recomputeFeature(); //just a repaint. multiView is already marked for recompute by changed to Scale Gui::Command::updateActive(); } void TaskProjGroup::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } } const char * TaskProjGroup::viewChkIndexToCStr(int index) { // Third Angle: FTL T FTRight // L F Right Rear // FBL B FBRight // // First Angle: FBRight B FBL // Right F L Rear // FTRight T FTL assert (multiView != NULL); bool thirdAngle = multiView->usedProjectionType().isValue("Third Angle"); switch(index) { case 0: return (thirdAngle ? "FrontTopLeft" : "FrontBottomRight"); case 1: return (thirdAngle ? "Top" : "Bottom"); case 2: return (thirdAngle ? "FrontTopRight" : "FrontBottomLeft"); case 3: return (thirdAngle ? "Left" : "Right"); case 4: return (thirdAngle ? "Front" : "Front"); case 5: return (thirdAngle ? "Right" : "Left"); case 6: return (thirdAngle ? "Rear" : "Rear"); case 7: return (thirdAngle ? "FrontBottomLeft" : "FrontTopRight"); case 8: return (thirdAngle ? "Bottom" : "Top"); case 9: return (thirdAngle ? "FrontBottomRight" : "FrontTopLeft"); default: return NULL; } } void TaskProjGroup::setupViewCheckboxes(bool addConnections) { if ( multiView == NULL ) { return; } // There must be a better way to construct this list... QCheckBox * viewCheckboxes[] = { ui->chkView0, ui->chkView1, ui->chkView2, ui->chkView3, ui->chkView4, ui->chkView5, ui->chkView6, ui->chkView7, ui->chkView8, ui->chkView9 }; for (int i = 0; i < 10; ++i) { QCheckBox *box = viewCheckboxes[i]; if (addConnections) { connect(box, SIGNAL(toggled(bool)), this, SLOT(viewToggled(bool))); } const char *viewStr = viewChkIndexToCStr(i); if ( viewStr != NULL && multiView->hasProjection(viewStr) ) { box->setCheckState(Qt::Checked); } else { box->setCheckState(Qt::Unchecked); } } } void TaskProjGroup::setUiPrimary() { Base::Vector3d frontDir = multiView->getAnchorDirection(); ui->lePrimary->setText(formatVector(frontDir)); } //should return a configuration? frontdir,upDir mapped in DPG std::pair TaskProjGroup::get3DViewDir() { std::pair result; Base::Vector3d viewDir(0.0,-1.0,0.0); //default to front Base::Vector3d viewUp(0.0,0.0,1.0); //default to top std::list mdis = Gui::Application::Instance->activeDocument()->getMDIViews(); Gui::View3DInventor *view; Gui::View3DInventorViewer *viewer = nullptr; for (auto& m: mdis) { //find the 3D viewer view = dynamic_cast(m); if (view) { viewer = view->getViewer(); break; } } if (!viewer) { Base::Console().Log("LOG - TaskProjGroup could not find a 3D viewer\n"); return std::make_pair( viewDir, viewUp); } SbVec3f dvec = viewer->getViewDirection(); SbVec3f upvec = viewer->getUpDirection(); viewDir = Base::Vector3d(dvec[0], dvec[1], dvec[2]); viewUp = Base::Vector3d(upvec[0],upvec[1],upvec[2]); viewDir *= -1.0; //Inventor dir is opposite TD dir, Inventor up is same as TD up viewDir = DrawUtil::closestBasis(viewDir); viewUp = DrawUtil::closestBasis(viewUp); result = std::make_pair(viewDir,viewUp); return result; } QString TaskProjGroup::formatVector(Base::Vector3d v) { QString data = QString::fromLatin1("[%1 %2 %3]") .arg(QLocale::system().toString(v.x, 'f', 2)) .arg(QLocale::system().toString(v.y, 'f', 2)) .arg(QLocale::system().toString(v.z, 'f', 2)); return data; } bool TaskProjGroup::accept() { Gui::Document* doc = Gui::Application::Instance->getDocument(multiView->getDocument()); if (!doc) return false; if (!getCreateMode()) { //this is an edit session, end the transaction Gui::Command::commitCommand(); } //Gui::Command::updateActive(); //no chain of updates here Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); return true; } bool TaskProjGroup::reject() { Gui::Document* doc = Gui::Application::Instance->getDocument(multiView->getDocument()); if (!doc) return false; if (getCreateMode()) { std::string multiViewName = multiView->getNameInDocument(); std::string PageName = multiView->findParentPage()->getNameInDocument(); Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().%s.purgeProjections()", multiViewName.c_str()); Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().%s.removeView(App.activeDocument().%s)", PageName.c_str(),multiViewName.c_str()); Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().removeObject('%s')",multiViewName.c_str()); Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); //make sure any dangling objects are cleaned up Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().recompute()"); } else { if (Gui::Command::hasPendingCommand()) { std::vector undos = Gui::Application::Instance->activeDocument()->getUndoVector(); Gui::Application::Instance->activeDocument()->undo(1); multiView->rebuildViewList(); } else { Base::Console().Log("TaskProjGroup: Edit mode - NO command is active\n"); } Gui::Command::updateActive(); Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); } return false; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TODO: Do we really need to hang on to the TaskDlgProjGroup in this class? IR TaskDlgProjGroup::TaskDlgProjGroup(TechDraw::DrawProjGroup* featView, bool mode) : TaskDialog() , viewProvider(nullptr) , multiView(featView) { //viewProvider = dynamic_cast(featView); widget = new TaskProjGroup(featView,mode); taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/techdraw-projgroup"), widget->windowTitle(), true, 0); taskbox->groupLayout()->addWidget(widget); Content.push_back(taskbox); } TaskDlgProjGroup::~TaskDlgProjGroup() { } void TaskDlgProjGroup::update() { widget->updateTask(); } void TaskDlgProjGroup::setCreateMode(bool b) { widget->setCreateMode(b); } //==== calls from the TaskView =============================================================== void TaskDlgProjGroup::open() { if (!widget->getCreateMode()) { //this is an edit session, start a transaction Gui::Command::openCommand("Edit Projection Group"); } } void TaskDlgProjGroup::clicked(int) { } bool TaskDlgProjGroup::accept() { widget->accept(); return true; } bool TaskDlgProjGroup::reject() { widget->reject(); return true; } #include