commit e20fa428dd4de8c96b5a169e86d523a508e14098 Author: keyeslll Date: Tue Feb 28 14:50:28 2023 +0800 添加项目文件。 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/BreakPad/QBreakpadHandler.h b/BreakPad/QBreakpadHandler.h new file mode 100644 index 0000000..4e72c88 --- /dev/null +++ b/BreakPad/QBreakpadHandler.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009 Aleksey Palazhchenko + * Copyright (C) 2014 Sergey Shambir + * Copyright (C) 2016 Alexander Makarov + * + * This file is a part of Breakpad-qt library. + * + * This library 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. + * + * 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 + * Lesser General Public License for more details. + * + */ + +#ifndef QBREAKPAD_HANDLER_H +#define QBREAKPAD_HANDLER_H + +#include +#include +#include "singletone/singleton.h" + +namespace google_breakpad { + class ExceptionHandler; + class MinidumpDescriptor; +} + +class QBreakpadHandlerPrivate; + +class QBreakpadHandler: public QObject +{ + Q_OBJECT +public: + static QString version(); + + QBreakpadHandler(); + ~QBreakpadHandler(); + + QString uploadUrl() const; + QString dumpPath() const; + QStringList dumpFileList() const; + + void setDumpPath(const QString& path); + void setUploadUrl(const QUrl& url); + +public slots: + void sendDumps(); + +private: + QBreakpadHandlerPrivate* d; +}; +#define QBreakpadInstance Singleton::instance() + +#endif // QBREAKPAD_HANDLER_H diff --git a/BreakPad/QBreakpadHttpUploader.h b/BreakPad/QBreakpadHttpUploader.h new file mode 100644 index 0000000..08ff38b --- /dev/null +++ b/BreakPad/QBreakpadHttpUploader.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 Aleksey Palazhchenko + * Copyright (C) 2014 Sergey Shambir + * Copyright (C) 2016 Alexander Makarov + * + * This file is a part of Breakpad-qt library. + * + * This library 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. + * + * 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 + * Lesser General Public License for more details. + * + */ + +#ifndef QBREAKPAD_HTTP_SENDER_H +#define QBREAKPAD_HTTP_SENDER_H + +#include +#include +#include +#include +#include + +class QString; +class QUrl; +class QFile; + +class QBreakpadHttpUploader : public QObject +{ + Q_OBJECT +public: + QBreakpadHttpUploader(QObject *parent=0); + QBreakpadHttpUploader(const QUrl& url, QObject *parent=0); + ~QBreakpadHttpUploader(); + + //TODO: proxy, ssl + QString remoteUrl() const; + void setUrl(const QUrl& url); + +signals: + void finished(QString answer); + +public slots: + void uploadDump(const QString& abs_file_path); + +private slots: + void onUploadProgress(qint64 sent, qint64 total); + void onError(QNetworkReply::NetworkError err); + void onUploadFinished(); + +private: + QNetworkAccessManager m_manager; + QNetworkRequest m_request; + QPointer m_reply; + QFile* m_file; +}; + +#endif // QBREAKPAD_HTTP_SENDER_H diff --git a/BreakPad/singletone/call_once.h b/BreakPad/singletone/call_once.h new file mode 100644 index 0000000..d2afe50 --- /dev/null +++ b/BreakPad/singletone/call_once.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 Alexander Makarov + * + * Source: + * https://wiki.qt.io/Qt_thread-safe_singleton + * + * This file is a part of Breakpad-qt library. + * + * This library 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. + * + * 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 + * Lesser General Public License for more details. + * + */ + + +#ifndef CALL_ONCE +#define CALL_ONCE + +#include +#include +#include +#include +#include +#include + +namespace CallOnce { + enum ECallOnce { + CO_Request, + CO_InProgress, + CO_Finished + }; + + Q_GLOBAL_STATIC(QThreadStorage, once_flag) +} + +template +inline static void qCallOnce(Function func, QBasicAtomicInt& flag) +{ + using namespace CallOnce; + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + int protectFlag = flag.fetchAndStoreAcquire(flag); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + int protectFlag = flag.fetchAndStoreAcquire(flag.loadRelaxed()); +#else + int protectFlag = flag.fetchAndStoreAcquire(flag.load()); +#endif + + if (protectFlag == CO_Finished) + return; + if (protectFlag == CO_Request && flag.testAndSetRelaxed(protectFlag, + CO_InProgress)) { + func(); + flag.fetchAndStoreRelease(CO_Finished); + } + else { + do { + QThread::yieldCurrentThread(); + } + while (!flag.testAndSetAcquire(CO_Finished, CO_Finished)); + } +} + +template +inline static void qCallOncePerThread(Function func) +{ + using namespace CallOnce; + if (!once_flag()->hasLocalData()) { + once_flag()->setLocalData(new QAtomicInt(CO_Request)); + qCallOnce(func, *once_flag()->localData()); + } +} + +#endif // CALL_ONCE + diff --git a/BreakPad/singletone/singleton.h b/BreakPad/singletone/singleton.h new file mode 100644 index 0000000..d611688 --- /dev/null +++ b/BreakPad/singletone/singleton.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Alexander Makarov + * + * Source: + * https://wiki.qt.io/Qt_thread-safe_singleton + * + * This file is a part of Breakpad-qt library. + * + * This library 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. + * + * 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 + * Lesser General Public License for more details. + * + */ + +#ifndef SINGLETON +#define SINGLETON + +#include +#include +#include "call_once.h" + +template +class Singleton +{ +public: + static T& instance() + { + qCallOnce(init, flag); + return *tptr; + } + + static void init() + { + tptr.reset(new T); + } + +private: + Singleton() {} + ~Singleton() {} + Q_DISABLE_COPY(Singleton) + + static QScopedPointer tptr; + static QBasicAtomicInt flag; +}; + +template QScopedPointer Singleton::tptr(0); +template QBasicAtomicInt Singleton::flag = Q_BASIC_ATOMIC_INITIALIZER(CallOnce::CO_Request); + +#endif // SINGLETON + diff --git a/FlowProjects/domainReduce.flow b/FlowProjects/domainReduce.flow new file mode 100644 index 0000000..743f9ac --- /dev/null +++ b/FlowProjects/domainReduce.flow @@ -0,0 +1,118 @@ +{ + "connections": [ + { + "in_id": "{429a6e71-36d8-49e9-87f2-c3cbe1cacb31}", + "in_index": 0, + "out_id": "{5b9425ee-74a8-4e67-8185-7782bf860064}", + "out_index": 0 + }, + { + "in_id": "{81429418-7d12-45bf-ab97-9e3741bd9353}", + "in_index": 0, + "out_id": "{429a6e71-36d8-49e9-87f2-c3cbe1cacb31}", + "out_index": 0 + }, + { + "in_id": "{8eb4a83d-d4f1-4580-84b3-4e70d5f3852c}", + "in_index": 0, + "out_id": "{81429418-7d12-45bf-ab97-9e3741bd9353}", + "out_index": 0 + } + ], + "nodes": [ + { + "id": "{8eb4a83d-d4f1-4580-84b3-4e70d5f3852c}", + "model": { + "name": "图像显示" + }, + "position": { + "x": 853, + "y": 288 + } + }, + { + "id": "{5b9425ee-74a8-4e67-8185-7782bf860064}", + "model": { + "curIndex": 9, + "folderPath": "B:/HalconWorkSpace/deeplearning/seg_term/image/term1/core", + "name": "图像目录输入" + }, + "position": { + "x": 104, + "y": 216 + } + }, + { + "id": "{429a6e71-36d8-49e9-87f2-c3cbe1cacb31}", + "model": { + "m_region_data": { + "shapeMode": { + "data": [ + 1, + 2 + ] + }, + "shapePolygon": { + "data": [ + { + "poly_data": [ + { + "x": 174.97777777777776, + "y": 291.6 + }, + { + "x": 1076.6222222222223, + "y": 291.6 + }, + { + "x": 1076.6222222222223, + "y": 799.2888888888889 + }, + { + "x": 174.97777777777776, + "y": 799.2888888888889 + } + ] + }, + { + "poly_data": [ + { + "x": 440.93333333333334, + "y": 409.6444444444445 + }, + { + "x": 662.7555555555557, + "y": 409.6444444444445 + }, + { + "x": 662.7555555555557, + "y": 598.7555555555556 + }, + { + "x": 440.93333333333334, + "y": 598.7555555555556 + } + ] + } + ] + } + }, + "name": "选区掩膜" + }, + "position": { + "x": 429, + "y": 362 + } + }, + { + "id": "{81429418-7d12-45bf-ab97-9e3741bd9353}", + "model": { + "name": "图像转灰度" + }, + "position": { + "x": 666, + "y": 262 + } + } + ] +} diff --git a/FlowProjects/test.flow b/FlowProjects/test.flow new file mode 100644 index 0000000..7529fa7 --- /dev/null +++ b/FlowProjects/test.flow @@ -0,0 +1,115 @@ +{ + "connections": [ + { + "in_id": "{a340f2ff-e098-494e-aa9b-ec319ad5c469}", + "in_index": 0, + "out_id": "{81429418-7d12-45bf-ab97-9e3741bd9353}", + "out_index": 0 + }, + { + "in_id": "{81429418-7d12-45bf-ab97-9e3741bd9353}", + "in_index": 0, + "out_id": "{5b9425ee-74a8-4e67-8185-7782bf860064}", + "out_index": 0 + }, + { + "in_id": "{8eb4a83d-d4f1-4580-84b3-4e70d5f3852c}", + "in_index": 1, + "out_id": "{d7c8188e-0aa7-420d-b7aa-89bea6f48f31}", + "out_index": 0 + }, + { + "in_id": "{d7c8188e-0aa7-420d-b7aa-89bea6f48f31}", + "in_index": 0, + "out_id": "{a340f2ff-e098-494e-aa9b-ec319ad5c469}", + "out_index": 0 + } + ], + "nodes": [ + { + "id": "{5b9425ee-74a8-4e67-8185-7782bf860064}", + "model": { + "curIndex": 2, + "folderPath": "B:/TestData/vision/0215", + "name": "图像目录输入" + }, + "position": { + "x": 203.50000000000003, + "y": 142.16666666666669 + } + }, + { + "id": "{8eb4a83d-d4f1-4580-84b3-4e70d5f3852c}", + "model": { + "name": "图像显示" + }, + "position": { + "x": 830.1111111111111, + "y": 151.74999999999994 + } + }, + { + "id": "{d7c8188e-0aa7-420d-b7aa-89bea6f48f31}", + "model": { + "m_maxGraySlider": 255, + "m_minGraySlider": 0, + "name": "二值化节点" + }, + "position": { + "x": 439.83333333333337, + "y": 604.6666666666667 + } + }, + { + "id": "{81429418-7d12-45bf-ab97-9e3741bd9353}", + "model": { + "name": "图像转灰度" + }, + "position": { + "x": 543.0787037037037, + "y": 275.43055555555566 + } + }, + { + "id": "{a340f2ff-e098-494e-aa9b-ec319ad5c469}", + "model": { + "m_region_data": { + "shapeMode": { + "data": [ + 1 + ] + }, + "shapePolygon": { + "data": [ + { + "poly_data": [ + { + "x": 795.3333333333337, + "y": 1565.4666666666662 + }, + { + "x": 875.3333333333337, + "y": 1565.4666666666662 + }, + { + "x": 875.3333333333337, + "y": 1645.4666666666662 + }, + { + "x": 795.3333333333337, + "y": 1645.4666666666662 + } + ] + } + ] + } + }, + "name": "选区掩膜" + }, + "position": { + "x": 641.6666666666667, + "y": 165.83333333333334 + } + } + ] +} diff --git a/NodeEditorPro.sln b/NodeEditorPro.sln new file mode 100644 index 0000000..85bd686 --- /dev/null +++ b/NodeEditorPro.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32825.248 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NodeEditorPro", "NodeEditorPro\NodeEditorPro.vcxproj", "{4911AD57-2763-4EDF-9C7C-2E197137BE8B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4911AD57-2763-4EDF-9C7C-2E197137BE8B}.Debug|x64.ActiveCfg = Debug|x64 + {4911AD57-2763-4EDF-9C7C-2E197137BE8B}.Debug|x64.Build.0 = Debug|x64 + {4911AD57-2763-4EDF-9C7C-2E197137BE8B}.Release|x64.ActiveCfg = Release|x64 + {4911AD57-2763-4EDF-9C7C-2E197137BE8B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {26A3A5F1-B946-441D-97D3-C988346DDEF3} + EndGlobalSection +EndGlobal diff --git a/NodeEditorPro/DefaultStyle.json b/NodeEditorPro/DefaultStyle.json new file mode 100644 index 0000000..0066a00 --- /dev/null +++ b/NodeEditorPro/DefaultStyle.json @@ -0,0 +1,154 @@ +{ + "ConnectionStyle": { + "BackgroundColor": { + "alpha": 255, + "blue": 20, + "green": 20, + "red": 20 + }, + "ConstructionLineWidth": 2, + "HoveredColor": { + "alpha": 255, + "blue": 255, + "green": 255, + "red": 225 + }, + "LineWidth": 3, + "NormalColor": { + "alpha": 255, + "blue": 139, + "green": 139, + "red": 0 + }, + "PointDiameter": 10, + "SelectedColor": { + "alpha": 255, + "blue": 100, + "green": 100, + "red": 100 + }, + "SelectedHaloColor": { + "alpha": 255, + "blue": 0, + "green": 165, + "red": 255 + }, + "UseDataDefinedColors": false + }, + "FlowViewStyle": { + "BackgroundColor": { + "alpha": 255, + "blue": 53, + "green": 53, + "red": 53 + }, + "CoarseGridColor": { + "alpha": 255, + "blue": 25, + "green": 25, + "red": 25 + }, + "FineGridColor": { + "alpha": 255, + "blue": 60, + "green": 60, + "red": 60 + } + }, + "NodeStyle": { + "BackgroundColor": { + "alpha": 255, + "blue": 34, + "green": 34, + "red": 34 + }, + "ConnectionPointColor": { + "alpha": 255, + "blue": 169, + "green": 169, + "red": 169 + }, + "ConnectionPointDiameter": 8, + "ErrorColor": { + "alpha": 255, + "blue": 0, + "green": 0, + "red": 255 + }, + "FilledConnectionPointColor": { + "alpha": 255, + "blue": 255, + "green": 255, + "red": 0 + }, + "FontColorFaded": { + "alpha": 255, + "blue": 120, + "green": 120, + "red": 120 + }, + "FontColor": { + "alpha": 255, + "blue": 255, + "green": 255, + "red": 255 + }, + "GradientColor0": { + "alpha": 255, + "blue": 60, + "green": 60, + "red": 60 + }, + "GradientColor1": { + "alpha": 255, + "blue": 80, + "green": 80, + "red": 80 + }, + "GradientColor2": { + "alpha": 255, + "blue": 64, + "green": 64, + "red": 64 + }, + "GradientColor3": { + "alpha": 255, + "blue": 58, + "green": 58, + "red": 58 + }, + "HoveredPenWidth": 1.5, + "NormalBoundaryColor": { + "alpha": 255, + "blue": 255, + "green": 255, + "red": 255 + }, + "Opacity": 0.800000011920929, + "PenWidth": 1, + "SelectedBoundaryColor": { + "alpha": 255, + "blue": 0, + "green": 165, + "red": 255 + }, + "ShadowColor": { + "alpha": 255, + "blue": 20, + "green": 20, + "red": 20 + }, + "TitleColor": { + "alpha": 255, + "blue": 12, + "green": 12, + "red": 12 + }, + "WarningColor": { + "alpha": 255, + "blue": 0, + "green": 128, + "red": 128 + } + } +} diff --git a/NodeEditorPro/Halcon2105.props b/NodeEditorPro/Halcon2105.props new file mode 100644 index 0000000..27f37d6 --- /dev/null +++ b/NodeEditorPro/Halcon2105.props @@ -0,0 +1,16 @@ + + + + + + + + E:\CodeDeps\Halcon2105Cpp\include;E:\CodeDeps\Halcon2105Cpp\include\halconcpp;E:\CodeDeps\Halcon2105Cpp\;%(AdditionalIncludeDirectories) + + + E:\CodeDeps\Halcon2105Cpp\lib;%(AdditionalLibraryDirectories) + halconcpp.lib;halcon.lib;%(AdditionalDependencies) + + + + \ No newline at end of file diff --git a/NodeEditorPro/NodeEditorPro.vcxproj b/NodeEditorPro/NodeEditorPro.vcxproj new file mode 100644 index 0000000..f1d1913 --- /dev/null +++ b/NodeEditorPro/NodeEditorPro.vcxproj @@ -0,0 +1,303 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {4911AD57-2763-4EDF-9C7C-2E197137BE8B} + QtVS_v304 + 10.0.22000.0 + 10.0.19041.0 + $(MSBuildProjectDirectory)\QtMsBuild + + + + Application + v142 + + + Application + v143 + + + + + + + 5.14.2_msvc2017_64 + core;opengl;network;gui;widgets; + debug + + + 5.15.2_msvc2019_64 + core;opengl;gui;widgets; + release + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)include;$(ProjectDir)examples;$(ProjectDir)src;$(ProjectDir)..\BreakPad;D:\vs_save\CODE-QT\VisionFlowPro-master\NodeEditorPro\examples\opcv;$(ProjectDir)include\nodes;$(ProjectDir)..\ShapeDrawer;..\NodeEditorPro;..\NodeEditorPro\NodeEditorPro;$(IncludePath) + + + $(ProjectDir)include;$(ProjectDir)examples;$(ProjectDir)src;$(ProjectDir)include\nodes;$(ProjectDir)..\ShapeDrawer;..\NodeEditorPro;..\NodeEditorPro\NodeEditorPro;$(IncludePath) + + + + stdcpp20 + D:\programs\Halcon18\include;D:\programs\opencv4.7.0\include;D:\programs\opencv4.7.0\include\opencv2;%(AdditionalIncludeDirectories) + stdc11 + false + true + + + D:\programs\Halcon18\lib\x64-win64;D:\programs\opencv4.7.0\x64\vc16\lib;D:\programs\qBreakpad\lib\Debug;%(AdditionalLibraryDirectories) + opencv_world470d.lib;qBreakpad.lib;%(AdditionalDependencies) + + + + + stdcpp20 + + + + + true + true + ProgramDatabase + Disabled + MultiThreadedDebugDLL + + + Console + true + + + + + true + true + None + MinSpace + MultiThreadedDLL + + + Windows + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NodeEditorPro/NodeEditorPro.vcxproj.filters b/NodeEditorPro/NodeEditorPro.vcxproj.filters new file mode 100644 index 0000000..757276c --- /dev/null +++ b/NodeEditorPro/NodeEditorPro.vcxproj.filters @@ -0,0 +1,576 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {99349809-55BA-4b9d-BF79-8FDBB0286EB3} + ui + + + {639EADAA-A684-42e4-A9AD-28FC9BCB8F7C} + ts + + + {9a98fda8-feb3-4f88-8c2e-592a4de4472c} + + + {d24eeab8-793f-42fd-a74c-811a1f607589} + + + {c1e4a5fa-9994-43e5-bb12-96e91cf47d5e} + + + {4ebde7d5-53a9-47c7-be30-715a8a16d470} + + + {b32b7bd8-e89e-4d39-a813-904b62cf937a} + + + {29211f79-6b95-4f3d-9689-a7725e0a9716} + + + {d10913a6-d89c-429f-9399-8f43d3c9921e} + + + {e33d0eb7-423e-495d-96bb-71d4ecf57e5f} + + + {678d160a-53aa-4f3b-8683-cee586ef65bb} + + + {44ba4646-41f6-4e96-a09a-3e5175a147fa} + + + {35c78b53-ce64-4c9d-ab5f-c0a80039de73} + + + + + examples\images + + + examples\images + + + examples\images + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + src + + + src + + + examples\images + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + + + examples\images + + + examples\images + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + breakpad + + + breakpad + + + examples\opcv + + + examples\opcv + + + + + examples\images + + + src + + + src + + + src + + + src + + + src + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + include\nodes + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\calculator + + + examples\halcon + + + examples\halcon + + + examples\halcon + + + src + + + src + + + examples\images + + + examples\halcon + + + examples\calculator + + + examples\halcon + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + ShapeDrawer + + + examples\opcv + + + examples\opcv + + + examples\opcv + + + breakpad + + + breakpad + + + Form Files + + + include\nodes + + + + + Resource Files + + + + + + Resource Files + + + + + showcase + + + showcase + + + showcase + + + showcase + + + + + showcase + + + showcase + + + + + Form Files + + + \ No newline at end of file diff --git a/NodeEditorPro/examples/calculator/AdditionModel.hpp b/NodeEditorPro/examples/calculator/AdditionModel.hpp new file mode 100644 index 0000000..df715a0 --- /dev/null +++ b/NodeEditorPro/examples/calculator/AdditionModel.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include + +#include "NodeDataModel.hpp" + +#include "MathOperationDataModel.hpp" +#include "DecimalData.hpp" + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class AdditionModel : public MathOperationDataModel +{ +public: + + virtual + ~AdditionModel() {} + +public: + + QString + caption() const override + { + return QStringLiteral("加法"); + } + + QString + name() const override + { + return QStringLiteral("加法"); + } + +private: + + void + compute() override + { + PortIndex const outPortIndex = 0; + + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + if (n1 && n2) + { + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + _result = std::make_shared(n1->number() + + n2->number()); + } + else + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("未连接或运行失败!"); + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); + } +}; diff --git a/NodeEditorPro/examples/calculator/CMakeLists.txt b/NodeEditorPro/examples/calculator/CMakeLists.txt new file mode 100644 index 0000000..badf02f --- /dev/null +++ b/NodeEditorPro/examples/calculator/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE CPPS ./*.cpp ) + +add_executable(calculator ${CPPS}) + +target_link_libraries(calculator nodes) diff --git a/NodeEditorPro/examples/calculator/Converters.cpp b/NodeEditorPro/examples/calculator/Converters.cpp new file mode 100644 index 0000000..4cdcc8f --- /dev/null +++ b/NodeEditorPro/examples/calculator/Converters.cpp @@ -0,0 +1,46 @@ +#include "Converters.hpp" + +#include + +#include "DecimalData.hpp" +#include "IntegerData.hpp" + + +std::shared_ptr +DecimalToIntegerConverter:: +operator()(std::shared_ptr data) +{ + auto numberData = + std::dynamic_pointer_cast(data); + + if (numberData) + { + _integer = std::make_shared(numberData->number()); + } + else + { + _integer.reset(); + } + + return _integer; +} + + +std::shared_ptr +IntegerToDecimalConverter:: +operator()(std::shared_ptr data) +{ + auto numberData = + std::dynamic_pointer_cast(data); + + if (numberData) + { + _decimal = std::make_shared(numberData->number()); + } + else + { + _decimal.reset(); + } + + return _decimal; +} diff --git a/NodeEditorPro/examples/calculator/Converters.hpp b/NodeEditorPro/examples/calculator/Converters.hpp new file mode 100644 index 0000000..cd0095b --- /dev/null +++ b/NodeEditorPro/examples/calculator/Converters.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "DecimalData.hpp" +#include "IntegerData.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; + +class DecimalData; +class IntegerData; + + +class DecimalToIntegerConverter +{ + +public: + + std::shared_ptr + operator()(std::shared_ptr data); + +private: + + std::shared_ptr _integer; +}; + + +class IntegerToDecimalConverter +{ + +public: + + std::shared_ptr + operator()(std::shared_ptr data); + +private: + + std::shared_ptr _decimal; +}; diff --git a/NodeEditorPro/examples/calculator/DecimalData.hpp b/NodeEditorPro/examples/calculator/DecimalData.hpp new file mode 100644 index 0000000..e9190f3 --- /dev/null +++ b/NodeEditorPro/examples/calculator/DecimalData.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "NodeDataModel.hpp" + +using QtNodes::NodeDataType; +using QtNodes::NodeData; + +/// The class can potentially incapsulate any user data which +/// need to be transferred within the Node Editor graph +class DecimalData : public NodeData +{ +public: + + DecimalData() + : _number(0.0) + {} + + DecimalData(double const number) + : _number(number) + {} + + NodeDataType type() const override + { + return NodeDataType{ "decimal", + QStringLiteral("浮点数") }; + } + + double number() const + { + return _number; + } + + QString numberAsText() const + { + return QString::number(_number, 'f'); + } + +private: + + double _number; +}; diff --git a/NodeEditorPro/examples/calculator/DivisionModel.hpp b/NodeEditorPro/examples/calculator/DivisionModel.hpp new file mode 100644 index 0000000..1b494ec --- /dev/null +++ b/NodeEditorPro/examples/calculator/DivisionModel.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include "NodeDataModel.hpp" + +#include "MathOperationDataModel.hpp" + +#include "DecimalData.hpp" + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class DivisionModel : public MathOperationDataModel +{ +public: + + virtual + ~DivisionModel() {} + +public: + QString + caption() const override + { + return QStringLiteral("除法"); + } + + bool + portCaptionVisible(PortType portType, PortIndex portIndex) const override + { + Q_UNUSED(portType); Q_UNUSED(portIndex); + return true; + } + + QString + portCaption(PortType portType, PortIndex portIndex) const override + { + switch (portType) + { + case PortType::In: + if (portIndex == 0) + return QStringLiteral("除数"); + else if (portIndex == 1) + return QStringLiteral("被除数"); + + break; + + case PortType::Out: + return QStringLiteral("结果"); + + default: + break; + } + return QString(); + } + + QString + name() const override + { + return QStringLiteral("除法"); + } + +private: + + void + compute() override + { + PortIndex const outPortIndex = 0; + + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + if (n2 && (n2->number() == 0.0)) + { + modelValidationState = NodeValidationState::Error; + modelValidationError = QStringLiteral("被除数无法为0!"); + _result.reset(); + } + else if (n1 && n2) + { + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + _result = std::make_shared(n1->number() / + n2->number()); + } + else + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("未连接或运行失败!"); + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); + } +}; diff --git a/NodeEditorPro/examples/calculator/IntegerData.hpp b/NodeEditorPro/examples/calculator/IntegerData.hpp new file mode 100644 index 0000000..f5985b7 --- /dev/null +++ b/NodeEditorPro/examples/calculator/IntegerData.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "NodeDataModel.hpp" + +using QtNodes::NodeDataType; +using QtNodes::NodeData; + +/// The class can potentially incapsulate any user data which +/// need to be transferred within the Node Editor graph +class IntegerData : public NodeData +{ +public: + + IntegerData() + : _number(0.0) + {} + + IntegerData(int const number) + : _number(number) + {} + + NodeDataType type() const override + { + return NodeDataType{ "integer", + QStringLiteral("整数") }; + } + + int number() const + { + return _number; + } + + QString numberAsText() const + { + return QString::number(_number); + } + +private: + + int _number; +}; diff --git a/NodeEditorPro/examples/calculator/MathNodes.hpp b/NodeEditorPro/examples/calculator/MathNodes.hpp new file mode 100644 index 0000000..935d106 --- /dev/null +++ b/NodeEditorPro/examples/calculator/MathNodes.hpp @@ -0,0 +1,10 @@ +#pragma once + +//math nodes +#include "calculator/AdditionModel.hpp" +#include "calculator/DivisionModel.hpp" +#include "calculator/MultiplicationModel.hpp" +#include "calculator/SubtractionModel.hpp" +#include "calculator/NumberSourceDataModel.hpp" +#include "calculator/NumberDisplayDataModel.hpp" + diff --git a/NodeEditorPro/examples/calculator/MathOperationDataModel.cpp b/NodeEditorPro/examples/calculator/MathOperationDataModel.cpp new file mode 100644 index 0000000..b2beaf9 --- /dev/null +++ b/NodeEditorPro/examples/calculator/MathOperationDataModel.cpp @@ -0,0 +1,67 @@ +#include "MathOperationDataModel.hpp" + +#include "DecimalData.hpp" + +unsigned int +MathOperationDataModel:: +nPorts(PortType portType) const +{ + unsigned int result; + + if (portType == PortType::In) + result = 2; + else + result = 1; + + return result; +} + + +NodeDataType +MathOperationDataModel:: +dataType(PortType, PortIndex) const +{ + return DecimalData().type(); +} + + +std::shared_ptr +MathOperationDataModel:: +outData(PortIndex) +{ + return std::static_pointer_cast(_result); +} + + +void +MathOperationDataModel:: +setInData(std::shared_ptr data, PortIndex portIndex) +{ + auto numberData = + std::dynamic_pointer_cast(data); + + if (portIndex == 0) + { + _number1 = numberData; + } + else + { + _number2 = numberData; + } + compute(); +} + +NodeValidationState +MathOperationDataModel:: +validationState() const +{ + return modelValidationState; +} + + +QString +MathOperationDataModel:: +validationMessage() const +{ + return modelValidationError; +} diff --git a/NodeEditorPro/examples/calculator/MathOperationDataModel.hpp b/NodeEditorPro/examples/calculator/MathOperationDataModel.hpp new file mode 100644 index 0000000..96528e8 --- /dev/null +++ b/NodeEditorPro/examples/calculator/MathOperationDataModel.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include "NodeDataModel.hpp" + +#include + +class DecimalData; + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class MathOperationDataModel : public NodeDataModel +{ + Q_OBJECT + +public: + + virtual + ~MathOperationDataModel() {} + +public: + + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, + PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr data, PortIndex portIndex) override; + + QWidget* + embeddedWidget() override { return nullptr; } + + NodeValidationState + validationState() const override; + + QString + validationMessage() const override; + +protected: + + virtual void + compute() = 0; + +protected: + + std::weak_ptr _number1; + std::weak_ptr _number2; + + std::shared_ptr _result; + + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("未连接或运行失败!"); +}; diff --git a/NodeEditorPro/examples/calculator/ModuloModel.cpp b/NodeEditorPro/examples/calculator/ModuloModel.cpp new file mode 100644 index 0000000..24e7743 --- /dev/null +++ b/NodeEditorPro/examples/calculator/ModuloModel.cpp @@ -0,0 +1,118 @@ +#include "ModuloModel.hpp" + +#include + +#include "IntegerData.hpp" + +QJsonObject +ModuloModel:: +save() const +{ + QJsonObject modelJson; + + modelJson["name"] = name(); + + return modelJson; +} + + +unsigned int +ModuloModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 2; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + + +NodeDataType +ModuloModel:: +dataType(PortType, PortIndex) const +{ + return IntegerData().type(); +} + + +std::shared_ptr +ModuloModel:: +outData(PortIndex) +{ + return _result; +} + + +void +ModuloModel:: +setInData(std::shared_ptr data, PortIndex portIndex) +{ + auto numberData = + std::dynamic_pointer_cast(data); + + if (portIndex == 0) + { + _number1 = numberData; + } + else + { + _number2 = numberData; + } + + { + PortIndex const outPortIndex = 0; + + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + if (n2 && (n2->number() == 0.0)) + { + modelValidationState = NodeValidationState::Error; + modelValidationError = QStringLiteral("Division by zero error"); + _result.reset(); + } + else if (n1 && n2) + { + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + _result = std::make_shared(n1->number() % + n2->number()); + } + else + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("Missing or incorrect inputs"); + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); + } +} + + +NodeValidationState +ModuloModel:: +validationState() const +{ + return modelValidationState; +} + + +QString +ModuloModel:: +validationMessage() const +{ + return modelValidationError; +} diff --git a/NodeEditorPro/examples/calculator/ModuloModel.hpp b/NodeEditorPro/examples/calculator/ModuloModel.hpp new file mode 100644 index 0000000..7742f2e --- /dev/null +++ b/NodeEditorPro/examples/calculator/ModuloModel.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include +#include + +#include "NodeDataModel.hpp" + +#include + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +class IntegerData; + +class ModuloModel + : public NodeDataModel +{ + Q_OBJECT + +public: + ModuloModel() = default; + + virtual + ~ModuloModel() = default; + +public: + + QString + caption() const override + { + return QStringLiteral("求模"); + } + + bool + captionVisible() const override + { + return true; + } + + bool + portCaptionVisible(PortType, PortIndex) const override + { + return true; + } + + QString + portCaption(PortType portType, PortIndex portIndex) const override + { + switch (portType) + { + case PortType::In: + if (portIndex == 0) + return QStringLiteral("求模数"); + else if (portIndex == 1) + return QStringLiteral("被模数"); + + break; + + case PortType::Out: + return QStringLiteral("结果"); + + default: + break; + } + return QString(); + } + + QString + name() const override + { + return QStringLiteral("求模"); + } + +public: + + QJsonObject + save() const override; + +public: + + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return nullptr; } + + NodeValidationState + validationState() const override; + + QString + validationMessage() const override; + +private: + + std::weak_ptr _number1; + std::weak_ptr _number2; + + std::shared_ptr _result; + + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("未连接或运行失败!"); +}; diff --git a/NodeEditorPro/examples/calculator/MultiplicationModel.hpp b/NodeEditorPro/examples/calculator/MultiplicationModel.hpp new file mode 100644 index 0000000..75a109d --- /dev/null +++ b/NodeEditorPro/examples/calculator/MultiplicationModel.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include "NodeDataModel.hpp" + +#include "MathOperationDataModel.hpp" + +#include "DecimalData.hpp" + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class MultiplicationModel : public MathOperationDataModel +{ +public: + + virtual + ~MultiplicationModel() {} + +public: + + QString + caption() const override + { + return QStringLiteral("乘法"); + } + + QString + name() const override + { + return QStringLiteral("乘法"); + } + +private: + + void + compute() override + { + PortIndex const outPortIndex = 0; + + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + if (n1 && n2) + { + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + _result = std::make_shared(n1->number() * + n2->number()); + } + else + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("未连接或输入错误!"); + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); + } +}; diff --git a/NodeEditorPro/examples/calculator/NumberDisplayDataModel.cpp b/NodeEditorPro/examples/calculator/NumberDisplayDataModel.cpp new file mode 100644 index 0000000..518068e --- /dev/null +++ b/NodeEditorPro/examples/calculator/NumberDisplayDataModel.cpp @@ -0,0 +1,102 @@ +#include "NumberDisplayDataModel.hpp" + +#include "DecimalData.hpp" + +#include + +NumberDisplayDataModel:: +NumberDisplayDataModel() + : _label{nullptr} +{ +} + + +unsigned int +NumberDisplayDataModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 0; + + default: + break; + } + + return result; +} + + +NodeDataType +NumberDisplayDataModel:: +dataType(PortType, PortIndex) const +{ + return DecimalData().type(); +} + + +std::shared_ptr +NumberDisplayDataModel:: +outData(PortIndex) +{ + std::shared_ptr ptr; + return ptr; +} + + +void +NumberDisplayDataModel:: +setInData(std::shared_ptr data, int) +{ + auto numberData = std::dynamic_pointer_cast(data); + + if (numberData) + { + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + _label->setText(numberData->numberAsText()); + } + else + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("Missing or incorrect inputs"); + _label->clear(); + } + + _label->adjustSize(); +} + +QWidget* +NumberDisplayDataModel:: +embeddedWidget() +{ + if (!_label) + { + _label = new QLabel(); + _label->setMargin(3); + } + + return _label; +} + +NodeValidationState +NumberDisplayDataModel:: +validationState() const +{ + return modelValidationState; +} + + +QString +NumberDisplayDataModel:: +validationMessage() const +{ + return modelValidationError; +} diff --git a/NodeEditorPro/examples/calculator/NumberDisplayDataModel.hpp b/NodeEditorPro/examples/calculator/NumberDisplayDataModel.hpp new file mode 100644 index 0000000..cdddc0d --- /dev/null +++ b/NodeEditorPro/examples/calculator/NumberDisplayDataModel.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" + +#include + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +class QLabel; + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class NumberDisplayDataModel : public NodeDataModel +{ + Q_OBJECT + +public: + NumberDisplayDataModel(); + + virtual + ~NumberDisplayDataModel() {} + +public: + + QString + caption() const override + { + return QStringLiteral("数字显示"); + } + + bool + captionVisible() const override + { + return true; + } + + QString + name() const override + { + return QStringLiteral("数字显示"); + } + +public: + + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, + PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr data, int) override; + + QWidget* + embeddedWidget() override; + + NodeValidationState + validationState() const override; + + QString + validationMessage() const override; + +private: + + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("未连接或运算失败!"); + + QLabel* _label; +}; diff --git a/NodeEditorPro/examples/calculator/NumberSourceDataModel.cpp b/NodeEditorPro/examples/calculator/NumberSourceDataModel.cpp new file mode 100644 index 0000000..982b0e3 --- /dev/null +++ b/NodeEditorPro/examples/calculator/NumberSourceDataModel.cpp @@ -0,0 +1,129 @@ +#include "NumberSourceDataModel.hpp" + +#include "DecimalData.hpp" + +#include +#include +#include + +NumberSourceDataModel:: +NumberSourceDataModel() + : _lineEdit{ nullptr } +{ +} + + +QJsonObject +NumberSourceDataModel:: +save() const +{ + QJsonObject modelJson = NodeDataModel::save(); + + if (_number) + modelJson["number"] = QString::number(_number->number()); + + return modelJson; +} + + +void +NumberSourceDataModel:: +restore(QJsonObject const& p) +{ + QJsonValue v = p["number"]; + + if (!v.isUndefined()) + { + QString strNum = v.toString(); + + bool ok; + double d = strNum.toDouble(&ok); + if (ok) + { + _number = std::make_shared(d); + _lineEdit->setText(strNum); + } + } +} + +unsigned int +NumberSourceDataModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 0; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + + +void +NumberSourceDataModel:: +onTextEdited(QString const& string) +{ + Q_UNUSED(string); + + bool ok = false; + + double number = _lineEdit->text().toDouble(&ok); + + if (ok) + { + _number = std::make_shared(number); + + Q_EMIT dataUpdated(0); + } + else + { + Q_EMIT dataInvalidated(0); + } +} + + +NodeDataType +NumberSourceDataModel:: +dataType(PortType, PortIndex) const +{ + return DecimalData().type(); +} + + +std::shared_ptr +NumberSourceDataModel:: +outData(PortIndex) +{ + return _number; +} + + +QWidget* +NumberSourceDataModel:: +embeddedWidget() +{ + if (!_lineEdit) + { + _lineEdit = new QLineEdit(); + + _lineEdit->setValidator(new QDoubleValidator()); + _lineEdit->setMaximumSize(_lineEdit->sizeHint()); + + connect(_lineEdit, &QLineEdit::textChanged, + this, &NumberSourceDataModel::onTextEdited); + + _lineEdit->setText("0.0"); + } + + return _lineEdit; +} diff --git a/NodeEditorPro/examples/calculator/NumberSourceDataModel.hpp b/NodeEditorPro/examples/calculator/NumberSourceDataModel.hpp new file mode 100644 index 0000000..105e1f5 --- /dev/null +++ b/NodeEditorPro/examples/calculator/NumberSourceDataModel.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" + +#include + +class DecimalData; + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +class QLineEdit; + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class NumberSourceDataModel + : public NodeDataModel +{ + Q_OBJECT + +public: + NumberSourceDataModel(); + + virtual + ~NumberSourceDataModel() {} + +public: + + QString + caption() const override + { + return QStringLiteral("数字输入"); + } + + bool + captionVisible() const override + { + return true; + } + + QString + name() const override + { + return QStringLiteral("数字输入"); + } + +public: + + QJsonObject + save() const override; + + void + restore(QJsonObject const& p) override; + +public: + + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override + { } + + QWidget* + embeddedWidget() override; + +private Q_SLOTS: + + void + onTextEdited(QString const& string); + +private: + + std::shared_ptr _number; + + QLineEdit* _lineEdit; +}; diff --git a/NodeEditorPro/examples/calculator/SubtractionModel.hpp b/NodeEditorPro/examples/calculator/SubtractionModel.hpp new file mode 100644 index 0000000..a54cf9e --- /dev/null +++ b/NodeEditorPro/examples/calculator/SubtractionModel.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include "NodeDataModel.hpp" + +#include "MathOperationDataModel.hpp" + +#include "DecimalData.hpp" + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class SubtractionModel : public MathOperationDataModel +{ +public: + + virtual + ~SubtractionModel() {} + +public: + + QString + caption() const override + { + return QStringLiteral("减法节点"); + } + + virtual bool + portCaptionVisible(PortType portType, PortIndex portIndex) const override + { + Q_UNUSED(portType); Q_UNUSED(portIndex); + return true; + } + + virtual QString + portCaption(PortType portType, PortIndex portIndex) const override + { + switch (portType) + { + case PortType::In: + if (portIndex == 0) + return QStringLiteral("减数"); + else if (portIndex == 1) + return QStringLiteral("被减数"); + + break; + + case PortType::Out: + return QStringLiteral("结果"); + + default: + break; + } + return QString(); + } + + QString + name() const override + { + return QStringLiteral("减法"); + } + +private: + + void + compute() override + { + PortIndex const outPortIndex = 0; + + auto n1 = _number1.lock(); + auto n2 = _number2.lock(); + + if (n1 && n2) + { + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + _result = std::make_shared(n1->number() - + n2->number()); + } + else + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失节点或运行失败!"); + _result.reset(); + } + + Q_EMIT dataUpdated(outPortIndex); + } +}; diff --git a/NodeEditorPro/examples/halcon/HImageDLSegmentModel.cpp b/NodeEditorPro/examples/halcon/HImageDLSegmentModel.cpp new file mode 100644 index 0000000..184132c --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageDLSegmentModel.cpp @@ -0,0 +1,166 @@ +#include "HImageDLSegmentModel.hpp" + +#include +#include +#include +#include + +#include "HRegionData.hpp" +#include "DrawShapeView.hpp" +#include "halconcpp/HalconCpp.h" +#include "QJsonParser.hpp" +using namespace HalconCpp; + + +HImageDLSegmentModel::HImageDLSegmentModel() +{ + m_hImage = std::make_shared(); + m_result = std::make_shared(); + btn_select_model = new QPushButton(QStringLiteral("选择模型")); + + connect(DrawShapeView::getInst(), SIGNAL(RegionFinished(RegionPixmapData)), + this, SLOT(OnNewRegionData(RegionPixmapData))); + + connect(btn_select_model, &QPushButton::clicked, [=]() + { + dl_path.clear(); + dl_path = + QFileDialog::getOpenFileName(nullptr, + tr("Select Dl Model"), + QDir::homePath(), + tr("File (*.hdl)")); + if (dl_path == "") + { + return; + } + readDlModel(dl_path); + }); +} + +bool HImageDLSegmentModel::RunTask() +{ + PortIndex const outPortIndex = 0; + try + { + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + return true; +} + +void HImageDLSegmentModel::OnNewRegionData(ShapeDataStruct _data) +{ + //if (!DrawShapeView::getInst()->getDrawFlag()) + //{ + // return; + //} + RunTask(); +} + +unsigned int HImageDLSegmentModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + case PortType::Out: + result = 1; + default: + break; + } + + return result; +} + +NodeValidationState HImageDLSegmentModel::validationState() const +{ + return modelValidationState; +} + +QString HImageDLSegmentModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HImageDLSegmentModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + result.insert("dl_path", dl_path); + return result; +} + +void HImageDLSegmentModel::restore(QJsonObject const& _json) +{ + dl_path = _json.value("dl_path").toString(); +} + +void HImageDLSegmentModel::readDlModel(QString modelFileName) +{ + m_dl_model = new HDlModel(); + m_dl_model->ReadDlModel(modelFileName.toStdString().c_str()); + image_dimensions = m_dl_model->GetDlModelParam("image_dimensions"); + class_ids = m_dl_model->GetDlModelParam("class_ids"); + m_dl_model->SetDlModelParam("batch_size", 1); +} + +NodeDataType +HImageDLSegmentModel::dataType(PortType port_type, PortIndex port_index) const +{ + if (port_type == PortType::In) + { + switch (port_index) + { + case 0: + return HImageData().type(); + break; + } + } + else + { + switch (port_index) + { + case 0: + return HRegionData().type(); + break; + } + } + return HImageData().type(); +} + +void HImageDLSegmentModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hImageData = + std::dynamic_pointer_cast(data); + if (hImageData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_hImage->setHImage(*hImageData->hImage()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HImageDLSegmentModel:: +outData(PortIndex) +{ + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HImageDLSegmentModel.hpp b/NodeEditorPro/examples/halcon/HImageDLSegmentModel.hpp new file mode 100644 index 0000000..78d109d --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageDLSegmentModel.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" +#include "DrawShapeView.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HImageDLSegmentModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageDLSegmentModel(); + virtual ~HImageDLSegmentModel() {} + +public: + QString caption() const override + { + return QStringLiteral("语义分割"); + } + QString name() const override + { + return QStringLiteral("语义分割"); + } + virtual QString modelName() const + { + return QStringLiteral("语义分割"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return btn_select_model; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + QJsonObject save() const override; + void restore(QJsonObject const&) override; + void readDlModel(QString modelFileName); +protected: + bool RunTask(); + +public slots: + void OnNewRegionData(ShapeDataStruct _data); +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: + QString dl_path; + QPushButton* btn_select_model; + HDlModel* m_dl_model; + HTuple image_dimensions; + HTuple class_ids; + HTuple valid_thres = 0.7; + std::shared_ptr m_hImage; + std::shared_ptr m_result; +}; diff --git a/NodeEditorPro/examples/halcon/HImageData.hpp b/NodeEditorPro/examples/halcon/HImageData.hpp new file mode 100644 index 0000000..5f2b50c --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageData.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" + +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using namespace HalconCpp; + +class HImageData :public NodeData +{ +public: + HImageData() + { + m_himage = HImage(); + } + HImageData(HImage& h_image) + { + if (h_image.IsInitialized()) + { + m_himage = h_image; + } + } + virtual ~HImageData() + { + + } + NodeDataType type() const override + { + return { "HImage","Img" }; + } + HImage* hImage() { return &m_himage; } + void setHImage(HImage const& _img) + { + if (!_img.IsInitialized()) + { + return; + } + m_himage = _img; + } +private: + HImage m_himage; +}; diff --git a/NodeEditorPro/examples/halcon/HImageFolderModel.cpp b/NodeEditorPro/examples/halcon/HImageFolderModel.cpp new file mode 100644 index 0000000..857cb04 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageFolderModel.cpp @@ -0,0 +1,171 @@ +#include "HImageFolderModel.hpp" +#include +#include +#include +#include +HImageFolderModel::HImageFolderModel() +{ + m_image_view = new HImageViewWidget(); + m_image_view->resize(200, 200); + m_paraWidget = new QWidget(); + m_paraWidget->resize(200, 250); + m_host = new QVBoxLayout(); + m_h_host = new QHBoxLayout(); + m_paraWidget->setLayout(m_host); + btn_selectFolder = new QPushButton(QStringLiteral("选择目录")); + btn_last = new QPushButton(QStringLiteral("上一张")); + btn_next = new QPushButton(QStringLiteral("下一张")); + + btn_selectFolder->setFixedHeight(25); + btn_last->setFixedHeight(25); + btn_next->setFixedHeight(25); + m_h_host->addWidget(btn_last); + m_h_host->addWidget(btn_next); + + m_host->addWidget(btn_selectFolder); + m_host->addLayout(m_h_host); + m_host->addWidget(m_image_view); + m_host->setSpacing(1); + m_h_host->setSpacing(1); + m_host->setContentsMargins(0, 0, 0, 0); + m_paraWidget->setContentsMargins(1, 1, 1, 1); + btn_last->installEventFilter(this); + btn_next->installEventFilter(this); + btn_selectFolder->installEventFilter(this); + + m_hImageData = std::make_shared(); +} + +unsigned int HImageFolderModel::nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 0; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +QJsonObject HImageFolderModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + result.insert("folderPath", folderPath); + result.insert("curIndex", curIndex); + return result; +} + +void HImageFolderModel::restore(QJsonObject const& json_values) +{ + //NodeDataModel::restore(json_values); + folderPath = json_values["folderPath"].toString(); + curIndex = json_values["curIndex"].toInt(0); + loadImageFolder(folderPath, curIndex); +} + +void HImageFolderModel::loadImageFolder(QString path, int index) +{ + QDir dir(folderPath); + if (folderPath == "" || !dir.exists()) + { + return; + } + HalconCpp::ListFiles(path.toStdString().c_str(), "files", &fileListStr); + TupleRegexpSelect(fileListStr, "\\.(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$", &imgListStr); + imageCounst = imgListStr.Length(); + if (imageCounst == 0) + { + return; + } + if (index >= imageCounst) + { + index = 0; + } + curIndex = index; + tmpImg.ReadImage(imgListStr[curIndex].ToTuple()); + m_hImageData->setHImage(tmpImg); + m_image_view->showImage(*m_hImageData->hImage()); +} + +bool HImageFolderModel::eventFilter(QObject* object, QEvent* event) +{ + if (object == btn_selectFolder) + { + if (event->type() == QEvent::MouseButtonPress) + { + folderPath = QFileDialog::getExistingDirectory(nullptr,tr("Select Folder"),QDir::homePath()); + if (folderPath == "") + { + return false; + } + + loadImageFolder(folderPath); + + Q_EMIT dataUpdated(0); + + return true; + } + else if (event->type() == QEvent::Resize) + { + + } + } + else if (object == btn_last) + { + if (event->type() == QEvent::MouseButtonPress) + { + if (curIndex - 1 >= 0) + { + curIndex--; + tmpImg.ReadImage(imgListStr[curIndex].ToTuple()); + m_hImageData->setHImage(tmpImg); + m_image_view->showImage(*m_hImageData->hImage()); + Q_EMIT dataUpdated(0); + } + else + { + curIndex = imageCounst - 1; + } + } + } + else if (object == btn_next) + { + if (event->type() == QEvent::MouseButtonPress) + { + if (curIndex + 1 < imageCounst) + { + curIndex++; + tmpImg.ReadImage(imgListStr[curIndex].ToTuple()); + m_hImageData->setHImage(tmpImg); + m_image_view->showImage(*m_hImageData->hImage()); + Q_EMIT dataUpdated(0); + } + else + { + curIndex = 0; + } + } + } + return false; +} + +NodeDataType +HImageFolderModel::dataType(PortType, PortIndex) const +{ + return HImageData().type(); +} +std::shared_ptr +HImageFolderModel:: +outData(PortIndex) +{ + return std::dynamic_pointer_cast(m_hImageData); +} diff --git a/NodeEditorPro/examples/halcon/HImageFolderModel.hpp b/NodeEditorPro/examples/halcon/HImageFolderModel.hpp new file mode 100644 index 0000000..067c2c0 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageFolderModel.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像输入节点 + */ +class HImageFolderModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageFolderModel(); + virtual ~HImageFolderModel() {} +public: + QString caption() const override + { + return QStringLiteral("图像目录输入"); + } + QString name() const override + { + return QStringLiteral("图像目录输入"); + } + virtual QString modelName() const + { + return QStringLiteral("图像目录输入"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override + { } + + QWidget* + embeddedWidget() override { return m_paraWidget; } + + bool + resizable() const override { return false; } + QJsonObject save() const override; + void restore(QJsonObject const&) override; + void loadImageFolder(QString path, int index = 0); +protected: + bool eventFilter(QObject* watched, QEvent* event) override; +private: + QString folderPath; + std::shared_ptr< HImageData> m_hImageData; + QWidget* m_paraWidget; + QVBoxLayout* m_host; + QHBoxLayout* m_h_host; + QPushButton* btn_selectFolder; + QPushButton* btn_last; + QPushButton* btn_next; + HImageViewWidget* m_image_view; + HTuple fileListStr; + HTuple imgListStr; + HImage tmpImg; + int curIndex = 0; + int imageCounst = 0; +}; diff --git a/NodeEditorPro/examples/halcon/HImageLoaderModel.cpp b/NodeEditorPro/examples/halcon/HImageLoaderModel.cpp new file mode 100644 index 0000000..2b858f7 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageLoaderModel.cpp @@ -0,0 +1,112 @@ +#include "HImageLoaderModel.hpp" +#include +#include +#include + +HImageLoaderModel::HImageLoaderModel() +{ + m_image_view = new HImageViewWidget(); + m_image_view->installEventFilter(this); + m_image_view->resize(200, 200); + m_hImageData = std::make_shared(); +} + +unsigned int HImageLoaderModel::nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 0; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +QJsonObject HImageLoaderModel::save() const +{ + QJsonObject modelJson = NodeDataModel::save(); + if (m_hImageData) + { + HTuple* R, * G, * B; + HTuple width, height; + int chanels = m_hImageData->hImage()->CountChannels(); + m_hImageData->hImage()->GetImageSize(&width, &height); + modelJson.insert("width", width.D()); + modelJson.insert("height", height.D()); + modelJson.insert("chanels", chanels); + modelJson.insert("imageName", imageName); + } + + return modelJson; +} + +void HImageLoaderModel::restore(QJsonObject const& p) +{ + imageName = p["imageName"].toString(); + loadImage(imageName); +} + +void HImageLoaderModel::loadImage(QString fileName) +{ + if (fileName == "") + { + return; + } + HImage tmpImg; + tmpImg.ReadImage(fileName.toStdString().c_str()); + m_hImageData->setHImage(tmpImg); + m_image_view->showImage(*m_hImageData->hImage()); +} + +bool HImageLoaderModel::eventFilter(QObject* object, QEvent* event) +{ + if (object == m_image_view) + { + if (event->type() == QEvent::MouseButtonPress) + { + imageName = + QFileDialog::getOpenFileName(nullptr, + tr("Open Image"), + QDir::homePath(), + tr("Image Files (*.png *.jpg *.bmp)")); + if (imageName == "") + { + return false; + } + + loadImage(imageName); + + Q_EMIT dataUpdated(0); + + return true; + } + else if (event->type() == QEvent::Resize) + { + + } + } + + return false; +} + +NodeDataType +HImageLoaderModel::dataType(PortType, PortIndex) const +{ + return HImageData().type(); +} + +std::shared_ptr +HImageLoaderModel:: +outData(PortIndex) +{ + return std::dynamic_pointer_cast(m_hImageData); +} diff --git a/NodeEditorPro/examples/halcon/HImageLoaderModel.hpp b/NodeEditorPro/examples/halcon/HImageLoaderModel.hpp new file mode 100644 index 0000000..9dbbca2 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageLoaderModel.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像输入节点 + */ +class HImageLoaderModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageLoaderModel(); + virtual ~HImageLoaderModel() {} +public: + QString caption() const override + { + return QStringLiteral("图像输入"); + } + QString name() const override + { + return QStringLiteral("图像输入"); + } + virtual QString modelName() const + { + return QStringLiteral("图像输入"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override + { } + + QWidget* + embeddedWidget() override { return m_image_view; } + + bool + resizable() const override { return false; } + QJsonObject save() const override; + void restore(QJsonObject const&) override; + void loadImage(QString fileName); +protected: + bool eventFilter(QObject* watched, QEvent* event) override; +private: + QString imageName; + std::shared_ptr< HImageData> m_hImageData; + HImageViewWidget* m_image_view; + +}; diff --git a/NodeEditorPro/examples/halcon/HImageRGB2GrayModel.cpp b/NodeEditorPro/examples/halcon/HImageRGB2GrayModel.cpp new file mode 100644 index 0000000..3f792bb --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageRGB2GrayModel.cpp @@ -0,0 +1,102 @@ +#include "HImageRGB2GrayModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HImageRGB2GrayModel::HImageRGB2GrayModel() +{ + m_hImage = std::make_shared(); +} + +bool HImageRGB2GrayModel::RunTask() +{ + Q_EMIT computingStarted(); + PortIndex const outPortIndex = 0; + try + { + HTuple imgChanels = m_hImage->hImage()->CountChannels(); + if (imgChanels == 3) + { + HImage tmp_img = m_hImage->hImage()->Rgb3ToGray(*m_hImage->hImage(), *m_hImage->hImage()); + m_hImage->setHImage(tmp_img); + tmp_img.Clear(); + } + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + Q_EMIT computingFinished(); + return true; +} + +unsigned int HImageRGB2GrayModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HImageRGB2GrayModel::validationState() const +{ + return modelValidationState; +} + +QString HImageRGB2GrayModel::validationMessage() const +{ + return modelValidationError; +} + +NodeDataType +HImageRGB2GrayModel::dataType(PortType, PortIndex) const +{ + return HImageData().type(); +} + +void HImageRGB2GrayModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hImageData = + std::dynamic_pointer_cast(data); + if (hImageData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_hImage->setHImage(*hImageData->hImage()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HImageRGB2GrayModel:: +outData(PortIndex) +{ + return std::dynamic_pointer_cast(m_hImage); +} diff --git a/NodeEditorPro/examples/halcon/HImageRGB2GrayModel.hpp b/NodeEditorPro/examples/halcon/HImageRGB2GrayModel.hpp new file mode 100644 index 0000000..e4b56b3 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageRGB2GrayModel.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HImageRGB2GrayModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageRGB2GrayModel(); + virtual ~HImageRGB2GrayModel() {} + +public: + QString caption() const override + { + return QStringLiteral("图像转灰度"); + } + QString name() const override + { + return QStringLiteral("图像转灰度"); + } + virtual QString modelName() const + { + return QStringLiteral("图像转灰度"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return Q_NULLPTR; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; +protected: + bool RunTask(); + +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: + std::shared_ptr m_hImage; + +}; diff --git a/NodeEditorPro/examples/halcon/HImageReduceDomainModel.cpp b/NodeEditorPro/examples/halcon/HImageReduceDomainModel.cpp new file mode 100644 index 0000000..ad36b45 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageReduceDomainModel.cpp @@ -0,0 +1,142 @@ +#include "HImageReduceDomainModel.hpp" + +#include +#include +#include "DrawShapeView.hpp" +#include "halconcpp/HalconCpp.h" +#include "QJsonParser.hpp" +using namespace HalconCpp; + + +HImageReduceDomainModel::HImageReduceDomainModel() +{ + m_hImage = std::make_shared(); + m_result = std::make_shared(); + btn_drawReg = new QPushButton(QStringLiteral("绘制区域")); + m_region_data = std::make_shared(); + m_domain.GenEmptyRegion(); + + connect(DrawShapeView::getInst(), SIGNAL(RegionComform(ShapeDataStruct)), + this, SLOT(OnNewRegionData(ShapeDataStruct))); + + connect(btn_drawReg, &QPushButton::clicked, [=]() + { + QPixmap tmpPix; + HImageViewWidget::HImageToQPixmap(*m_hImage->hImage(), tmpPix); + DrawShapeView::getInst()->FitShowImage(tmpPix, *m_region_data); + }); +} + +bool HImageReduceDomainModel::RunTask() +{ + //Q_EMIT computingStarted(); + PortIndex const outPortIndex = 0; + try + { + if ((int)m_region_data->shapePolygon.size() > 0) + { + HImage tmpImage; + HalconCpp::ReduceDomain(*m_hImage->hImage(), m_domain, &tmpImage); + m_result->setHImage(tmpImage); + } + else + { + m_result->setHImage(*m_hImage->hImage()); + } + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + return true; +} + +void HImageReduceDomainModel::OnNewRegionData(ShapeDataStruct _data) +{ + *m_region_data = std::move(_data); + m_domain = DrawShapeView::GetHRegionFromData(*m_region_data); + + RunTask(); +} + +unsigned int HImageReduceDomainModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HImageReduceDomainModel::validationState() const +{ + return modelValidationState; +} + +QString HImageReduceDomainModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HImageReduceDomainModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + result.insert("m_region_data", QJsonConvert::convertToJson(*m_region_data)); + return result; +} + +void HImageReduceDomainModel::restore(QJsonObject const& _json) +{ + QJsonConvert::convertFromJson(_json.value("m_region_data").toObject(), *m_region_data); + m_domain = DrawShapeView::GetHRegionFromData(*m_region_data); +} + +NodeDataType +HImageReduceDomainModel::dataType(PortType, PortIndex) const +{ + return HImageData().type(); +} + +void HImageReduceDomainModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hImageData = + std::dynamic_pointer_cast(data); + if (hImageData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_hImage->setHImage(*hImageData->hImage()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HImageReduceDomainModel:: +outData(PortIndex) +{ + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HImageReduceDomainModel.hpp b/NodeEditorPro/examples/halcon/HImageReduceDomainModel.hpp new file mode 100644 index 0000000..1c78d11 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageReduceDomainModel.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HImageViewWidget.hpp" +#include "DrawShapeView.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HImageReduceDomainModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageReduceDomainModel(); + virtual ~HImageReduceDomainModel() {} + +public: + QString caption() const override + { + return QStringLiteral("选区掩膜"); + } + QString name() const override + { + return QStringLiteral("选区掩膜"); + } + virtual QString modelName() const + { + return QStringLiteral("选区掩膜"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return btn_drawReg; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + QJsonObject save() const override; + void restore(QJsonObject const&) override; +protected: + bool RunTask(); + +public slots: + void OnNewRegionData(ShapeDataStruct _data); +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: + //HWindow* h_window; + QPushButton* btn_drawReg; + HRegion m_domain; + std::shared_ptr m_region_data; + std::shared_ptr m_hImage; + std::shared_ptr m_result; +}; diff --git a/NodeEditorPro/examples/halcon/HImageShowModel.cpp b/NodeEditorPro/examples/halcon/HImageShowModel.cpp new file mode 100644 index 0000000..dc9236c --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageShowModel.cpp @@ -0,0 +1,153 @@ +#include "HImageShowModel.hpp" +#include +#include +#include + +HImageShowModel::HImageShowModel() +{ + m_image_view = new HImageViewWidget(); + m_image_view->installEventFilter(this); + m_image_view->resize(200, 200); + m_hImage = std::make_shared(); + m_hRegion = std::make_shared(); +#ifdef SHOWHALCON_OBJ + h_window = new HWindow(0, 0, 512, 512, nullptr, "visible", ""); +#endif + +} + +bool HImageShowModel::RunTask() +{ + PortIndex const outPortIndex = 0; + + try + { + m_image_view->showImage(*m_hImage->hImage()); + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +unsigned int HImageShowModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 2; + break; + + case PortType::Out: + result = 2; + + default: + break; + } + + return result; +} + +NodeValidationState HImageShowModel::validationState() const +{ + return modelValidationState; +} + +QString HImageShowModel::validationMessage() const +{ + return modelValidationError; +} + +bool HImageShowModel::eventFilter(QObject* object, QEvent* event) +{ + + return false; +} + +NodeDataType +HImageShowModel::dataType(PortType, PortIndex index) const +{ + switch (index) + { + case 0: + return HImageData().type(); + break; + case 1: + return HRegionData().type(); + break; + } + return HImageData().type(); +} + +void HImageShowModel:: +setInData(std::shared_ptr data, int portIndex) +{ + if (data == nullptr) + { + return; + } + if (data->type() == m_hImage->type()) + { + auto dataPtr = std::dynamic_pointer_cast(data); + if (!dataPtr->hImage()->IsInitialized()) + { + return; + } + m_hImage->setHImage(*dataPtr->hImage()); +#ifdef SHOWHALCON_OBJ + h_window->ClearWindow(); + h_window->SetPart(HTuple(0), HTuple(0), m_hImage->hImage()->Height(), m_hImage->hImage()->Width()); + HTuple chanels = m_hImage->hImage()->CountChannels(); + if (chanels == 1) + { + h_window->DispImage(*m_hImage->hImage()); + } + else + { + h_window->DispColor(*m_hImage->hImage()); + } +#endif + } + else if (data->type() == m_hRegion->type()) + { + auto dataPtr = std::dynamic_pointer_cast(data); + if (!dataPtr->hRegion()->IsInitialized()) + { + return; + } + m_hRegion->setHRegion(*dataPtr->hRegion()); + m_hRegion->setSize(dataPtr->getSize()); + + HImage tmpImg = m_hRegion->hRegion()->RegionToBin(255, 0, + m_hRegion->getSize().width(), m_hRegion->getSize().height()); + m_hImage->setHImage(tmpImg); + } + RunTask(); +} + +std::shared_ptr +HImageShowModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_hImage); + break; + case 1: + return std::dynamic_pointer_cast(m_hRegion); + break; + } + return std::dynamic_pointer_cast(m_hImage); +} diff --git a/NodeEditorPro/examples/halcon/HImageShowModel.hpp b/NodeEditorPro/examples/halcon/HImageShowModel.hpp new file mode 100644 index 0000000..2536f71 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageShowModel.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; + +//#define SHOWHALCON_OBJ + +/** + * \brief halcon 图像输入节点 + */ +class HImageShowModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageShowModel(); + virtual ~HImageShowModel() {} + +public: + QString caption() const override + { + return QStringLiteral("图像显示"); + } + QString name() const override + { + return QStringLiteral("图像显示"); + } + virtual QString modelName() const + { + return QStringLiteral("图像显示"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return m_image_view; } + + bool + resizable() const override { return true; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: +#ifdef SHOWHALCON_OBJ + HWindow* h_window; +#endif + + std::shared_ptr m_hImage; + std::shared_ptr m_hRegion; + HImageViewWidget* m_image_view; +}; diff --git a/NodeEditorPro/examples/halcon/HImageSplitChanelModel.cpp b/NodeEditorPro/examples/halcon/HImageSplitChanelModel.cpp new file mode 100644 index 0000000..f440cc6 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageSplitChanelModel.cpp @@ -0,0 +1,140 @@ +#include "HImageSplitChanelModel.hpp" + +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HImageSplitChanelModel::HImageSplitChanelModel() +{ + m_hImage = std::make_shared(); +} + +bool HImageSplitChanelModel::RunTask() +{ + + auto img1 = m_hImage.lock(); + try + { + if (img1) + { + HTuple imgChanels = img1->hImage()->CountChannels(); + if (imgChanels == 3) + { + HImage chanR, chanG, chanB; + Decompose3(*img1->hImage(), &chanR, &chanG, &chanB); + if (m_resultR == nullptr) + { + m_resultR = std::make_shared(chanR); + } + if (m_resultG == nullptr) + { + m_resultG = std::make_shared(chanG); + } + if (m_resultB == nullptr) + { + m_resultB = std::make_shared(chanB); + } + m_resultR->setHImage(chanR); + m_resultG->setHImage(chanG); + m_resultB->setHImage(chanB); + } + else if (imgChanels == 1) + { + m_resultR = std::shared_ptr(m_hImage); + m_resultG = std::shared_ptr(m_hImage); + m_resultB = std::shared_ptr(m_hImage); + } + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + m_resultR.reset(); + m_resultG.reset(); + m_resultB.reset(); + } + + Q_EMIT dataUpdated((PortIndex)0); + Q_EMIT dataUpdated((PortIndex)1); + Q_EMIT dataUpdated((PortIndex)2); + + return true; +} + +unsigned int HImageSplitChanelModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 3; + + default: + break; + } + + return result; +} + +NodeValidationState HImageSplitChanelModel::validationState() const +{ + return modelValidationState; +} + +QString HImageSplitChanelModel::validationMessage() const +{ + return modelValidationError; +} + +NodeDataType +HImageSplitChanelModel::dataType(PortType, PortIndex) const +{ + return HImageData().type(); +} + +void HImageSplitChanelModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hImageData = + std::dynamic_pointer_cast(data); + + switch (portIndex) + { + case 0: + m_hImage = hImageData; + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HImageSplitChanelModel:: +outData(PortIndex index) +{ + + if (index == 0) + { + return std::static_pointer_cast(m_resultR); + } + else if (index == 1) + { + return std::static_pointer_cast(m_resultG); + } + else + { + return std::static_pointer_cast(m_resultB); + } +} diff --git a/NodeEditorPro/examples/halcon/HImageSplitChanelModel.hpp b/NodeEditorPro/examples/halcon/HImageSplitChanelModel.hpp new file mode 100644 index 0000000..e787ef8 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageSplitChanelModel.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HImageSplitChanelModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageSplitChanelModel(); + virtual ~HImageSplitChanelModel() {} + +public: + QString caption() const override + { + return QStringLiteral("SplitChanel"); + } + QString name() const override + { + return QStringLiteral("图像通道拆分"); + } + virtual QString modelName() const + { + return QStringLiteral("通道拆分"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return Q_NULLPTR; } + + bool + resizable() const override { return true; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; +protected: + bool RunTask(); + +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: + std::weak_ptr m_hImage; + std::shared_ptr m_resultR; + std::shared_ptr m_resultG; + std::shared_ptr m_resultB; +}; diff --git a/NodeEditorPro/examples/halcon/HImageThresholdModel.cpp b/NodeEditorPro/examples/halcon/HImageThresholdModel.cpp new file mode 100644 index 0000000..94fef17 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageThresholdModel.cpp @@ -0,0 +1,204 @@ +#include "HImageThresholdModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HImageThresholdModel::HImageThresholdModel() +{ + m_widget = new QWidget(); + m_widget->setAttribute(Qt::WA_NoSystemBackground); + m_widget->setFixedSize(130, 70); + m_minGraySlider = new QSlider(m_widget); + m_maxGraySlider = new QSlider(m_widget); + m_minGraySlider->setOrientation(Qt::Horizontal); + m_maxGraySlider->setOrientation(Qt::Horizontal); + m_minGraySlider->resize(120, 25); + m_maxGraySlider->resize(120, 25); + m_minGraySlider->move(0, 0); + m_maxGraySlider->move(0, 35); + m_minGraySlider->setMinimum(0); + m_minGraySlider->setMaximum(255); + m_maxGraySlider->setMinimum(0); + m_maxGraySlider->setMaximum(255); + + m_maxGraySlider->setValue(255); + + m_hImage = std::make_shared(); + m_domain = std::make_shared(); + m_result = std::make_shared(); + + connect(m_minGraySlider, &QSlider::valueChanged, [=]() + { + RunTask(); + }); + connect(m_maxGraySlider, &QSlider::valueChanged, [=]() + { + RunTask(); + }); +} + +bool HImageThresholdModel::RunTask() +{ + PortIndex const outPortIndex = 0; + if (m_hImage->hImage() == nullptr) + { + return false; + } + try + { + if (m_domain->hRegion()->IsInitialized()) + { + m_hImage->hImage()->ReduceDomain(*m_domain->hRegion()); + } + int imgChanels = m_hImage->hImage()->CountChannels(); + HImage tmp_img; + if (imgChanels == 3) + { + tmp_img = m_hImage->hImage()->Rgb3ToGray(*m_hImage->hImage(), *m_hImage->hImage()); + } + else if (imgChanels == 1) + { + tmp_img = *m_hImage->hImage(); + } + + double minVal = m_minGraySlider->value(); + double maxVal = m_maxGraySlider->value(); + + m_result->setHRegion(m_hImage->hImage()->Threshold(minVal, maxVal)); + m_result->setSize(QSize(m_hImage->hImage()->Width().D(), m_hImage->hImage()->Height().D())); + tmp_img.Clear(); + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool HImageThresholdModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int HImageThresholdModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 2; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HImageThresholdModel::validationState() const +{ + return modelValidationState; +} + +QString HImageThresholdModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HImageThresholdModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + result.insert("m_minGraySlider", m_minGraySlider->value()); + result.insert("m_maxGraySlider", m_maxGraySlider->value()); + return result; +} + +void HImageThresholdModel::restore(QJsonObject const& p) +{ + m_minGraySlider->setValue(p["m_minGraySlider"].toInt(0)); + m_maxGraySlider->setValue(p["m_maxGraySlider"].toInt(255)); +} + +NodeDataType +HImageThresholdModel::dataType(PortType portType, PortIndex portIndex) const +{ + if (portType == PortType::In) + { + switch (portIndex) + { + case 0: + return HImageData().type(); + break; + case 1: + return HRegionData().type(); + break; + } + } + else + { + switch (portIndex) + { + case 0: + return HRegionData().type(); + break; + } + } + return HImageData().type(); +} + +void HImageThresholdModel:: +setInData(std::shared_ptr data, int portIndex) +{ + if (portIndex == 0) + { + auto hImageData = + std::dynamic_pointer_cast(data); + if (hImageData == nullptr) + { + return; + } + m_hImage->setHImage(*hImageData->hImage()); + } + else if (portIndex == 1) + { + auto hImageData = + std::dynamic_pointer_cast(data); + if (hImageData != nullptr) + { + m_domain->setHRegion(*hImageData->hRegion()); + } + } + RunTask(); +} + +std::shared_ptr +HImageThresholdModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_result); + break; + case 1: + break; + default: + break; + } + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HImageThresholdModel.hpp b/NodeEditorPro/examples/halcon/HImageThresholdModel.hpp new file mode 100644 index 0000000..082bd6f --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageThresholdModel.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HImageThresholdModel :public NodeDataModel +{ + Q_OBJECT +public: + HImageThresholdModel(); + virtual ~HImageThresholdModel() = default; + +public: + QString caption() const override + { + return QStringLiteral("二值化节点"); + } + QString name() const override + { + return QStringLiteral("二值化节点"); + } + virtual QString modelName() const + { + return QStringLiteral("二值化节点"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return m_widget; } + + bool + resizable() const override { return true; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + + QJsonObject save() const override; + + void restore(QJsonObject const&) override; + +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: + std::shared_ptr m_hImage; + std::shared_ptr m_domain; + std::shared_ptr m_result; + QSlider* m_minGraySlider; + QSlider* m_maxGraySlider; + QWidget* m_widget; +}; diff --git a/NodeEditorPro/examples/halcon/HImageViewWidget.cpp b/NodeEditorPro/examples/halcon/HImageViewWidget.cpp new file mode 100644 index 0000000..f1918f1 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageViewWidget.cpp @@ -0,0 +1,158 @@ +#include "HImageViewWidget.hpp" + +HImageViewWidget::HImageViewWidget(QWidget* parent) + +{ + if (parent != Q_NULLPTR) + { + this->setParent(parent); + } + this->setStyleSheet("background-color:black;"); + cur_pixmap = new QPixmap(); +} + +void HImageViewWidget::showImage(HImage const& _himg) +{ + if (!_himg.IsInitialized()) + { + return; + } + Hlong width; + Hlong height; + double zoom_ratio = 1.0; + _himg.GetImageSize(&width, &height); + if (width > this->width()) + { + zoom_ratio = 1.0 * this->width() / width; + } + cur_image = _himg.ZoomImageSize(width * zoom_ratio, height * zoom_ratio, "bilinear"); + HImageToQPixmap(cur_image, *cur_pixmap); + this->update(); +} + +void HImageViewWidget::HImageToQPixmap(HImage const& _img, QPixmap& tar_pixmap) +{ + Hlong w, h; HString hType; + Hlong width; + Hlong height; + QImage tar_img; + + _img.GetImageSize(&width, &height); + HTuple type = _img.GetImageType(); + //获取HImage的通道数 + HTuple hChannels = _img.CountChannels(); + if (strcmp(type[0].S(), "byte")) // 如果不是 byte 类型,则失败 + { + return; + } + QImage::Format format; + switch (hChannels[0].I()) + { + case 1: + format = QImage::Format_Grayscale8; + break; + case 3: + format = QImage::Format_RGB32; + break; + default: + return; + } + if (tar_img.width() != width || tar_img.height() != height || tar_img.format() != format) + { + tar_img = QImage(static_cast(width), + static_cast(height), + format); + } + if (hChannels == 1) + { + //获取HImage的数据指针 + uchar* pBuf = (uchar*)_img.GetImagePointer1(&hType, &w, &h); + + //创建QImage图片 + tar_img = QImage(w, h, QImage::Format_Indexed8); + + //memcpy + for (int i = 0; i < h; i++, pBuf += w) + { + uchar* pDest = tar_img.scanLine(i); + memcpy(pDest, pBuf, w); + } + + //tar_pixmap = QPixmap::fromImage(tar_img); + } + else if (hChannels == 3) + { + uchar* R, * G, * B; + _img.GetImagePointer3(reinterpret_cast(&R), + reinterpret_cast(&G), + reinterpret_cast(&B), &hType, &width, &height); + + for (int row = 0; row < height; row++) + { + QRgb* line = reinterpret_cast(tar_img.scanLine(row)); + for (int col = 0; col < width; col++) + { + line[col] = qRgb(*R++, *G++, *B++); + } + } + } + tar_pixmap = QPixmap::fromImage(tar_img); +} + +void HImageViewWidget::QPixmapToHRegion(QPixmap const& _pix, HRegion& tar_reg) +{ + HImage tmpImag; + QImage tmpQImag = _pix.toImage(); + bool trans = HImageViewWidget::QImage2HImage(tmpQImag, tmpImag); + if (trans) + { + tar_reg = tmpImag.Threshold(100, 255); + } + tmpImag.Clear(); + +} + +/** + * @brief QImage2HImage 将 Qt QImage 转换为 Halcon 的 HImage + * @param from 输入的 QImage + * @param to 输出的 HImage ,from 和 to 不共享内存数据。 每次都会为 to 重新分配内存。 + * @return true 表示转换成功,false 表示转换失败。 + */ +bool HImageViewWidget::QImage2HImage(QImage& from, HalconCpp::HImage& to) +{ + if (from.isNull()) return false; + + int width = from.width(), height = from.height(); + QImage::Format format = from.format(); + + if (format == QImage::Format_RGB32 || + format == QImage::Format_ARGB32 || + format == QImage::Format_ARGB32_Premultiplied) + { + to.GenImageInterleaved(from.bits(), "rgbx", width, height, 0, "byte", width, height, 0, 0, 8, 0); + return true; + } + else if (format == QImage::Format_RGB888) + { + to.GenImageInterleaved(from.bits(), "rgb", width, height, 0, "byte", width, height, 0, 0, 8, 0); + return true; + } + else if (format == QImage::Format_Grayscale8 || format == QImage::Format_Indexed8) + { + to.GenImage1("byte", width, height, from.bits()); + return true; + } + return false; +} + + +void HImageViewWidget::paintEvent(QPaintEvent* event) +{ + //QPainter painter(this); + if (!cur_pixmap->isNull()) + { + this->setPixmap(cur_pixmap->scaled(this->width(), this->height(), Qt::KeepAspectRatio)); + } + QLabel::paintEvent(event); +} + diff --git a/NodeEditorPro/examples/halcon/HImageViewWidget.hpp b/NodeEditorPro/examples/halcon/HImageViewWidget.hpp new file mode 100644 index 0000000..b001e56 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HImageViewWidget.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "halconcpp/HalconCpp.h" +using namespace HalconCpp; + +class HImageViewWidget + :public QLabel +{ +public: + HImageViewWidget(QWidget* parent = Q_NULLPTR); + virtual ~HImageViewWidget() {} + void showImage(HImage const& _himg); +public: + static void HImageToQPixmap(HImage const& _img, QPixmap& tar_img); + static bool QImage2HImage(QImage& from, HImage& to); + static void QPixmapToHRegion(QPixmap const& _pix, HRegion& tar_img); +protected: + void paintEvent(QPaintEvent* event) override; +private: + HImage cur_image; + QPixmap* cur_pixmap; + // 实例化画家对象,this指定的是绘图设备 + QPainter painter; +}; + diff --git a/NodeEditorPro/examples/halcon/HObjectData.hpp b/NodeEditorPro/examples/halcon/HObjectData.hpp new file mode 100644 index 0000000..1547794 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HObjectData.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" + +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using namespace HalconCpp; + +class HObjecData :public NodeData +{ +public: + HObjecData() + { + m_hObject = HObject(); + } + HObjecData(HObject& _obj) + { + if (_obj.IsInitialized()) + { + m_hObject = _obj; + } + } + virtual ~HObjecData() + { + + } + NodeDataType type() const override + { + return { "HObject","data" }; + } + + HObject* hObject() { return &m_hObject; } + + void setObject(HObject const& _obj) + { + if (!_obj.IsInitialized()) + { + return; + } + m_hObject = _obj; + } +private: + HObject m_hObject; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionConnectModel.cpp b/NodeEditorPro/examples/halcon/HRegionConnectModel.cpp new file mode 100644 index 0000000..ee5b413 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionConnectModel.cpp @@ -0,0 +1,121 @@ +#include "HRegionConnectModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionConnectModel::HRegionConnectModel() +{ + m_InRegion = std::make_shared(); + m_result = std::make_shared(); + +} + +bool HRegionConnectModel::RunTask() +{ + PortIndex const outPortIndex = 0; + if (m_InRegion->hRegion() == nullptr) + { + return false; + } + try + { + m_result->setHRegion(m_InRegion->hRegion()->Connection()); + + m_result->setSize(m_InRegion->getSize()); + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool HRegionConnectModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int HRegionConnectModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionConnectModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionConnectModel::validationMessage() const +{ + return modelValidationError; +} + +NodeDataType +HRegionConnectModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionConnectModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hRegionData = + std::dynamic_pointer_cast(data); + if (hRegionData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_InRegion->setHRegion(*hRegionData->hRegion()); + m_InRegion->setSize(hRegionData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionConnectModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_result); + break; + case 1: + break; + default: + break; + } + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionConnectModel.hpp b/NodeEditorPro/examples/halcon/HRegionConnectModel.hpp new file mode 100644 index 0000000..8dcd14c --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionConnectModel.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HRegionConnectModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionConnectModel(); + virtual ~HRegionConnectModel() {} + +public: + QString caption() const override + { + return QStringLiteral("非联通区域"); + } + QString name() const override + { + return QStringLiteral("非联通区域"); + } + virtual QString modelName() const + { + return QStringLiteral("非联通区域"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return nullptr; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("区域输入未连接!"); +private: + std::shared_ptr m_InRegion; + std::shared_ptr m_result; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionData.hpp b/NodeEditorPro/examples/halcon/HRegionData.hpp new file mode 100644 index 0000000..bd88613 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionData.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" + +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using namespace HalconCpp; + +class HRegionData :public NodeData +{ +public: + HRegionData() + { + m_hRegion = HRegion(); + } + HRegionData(HRegion& _hregion) + { + if (_hregion.IsInitialized()) + { + m_hRegion = _hregion; + } + } + virtual ~HRegionData() + { + + } + NodeDataType type() const override + { + return { "HRegion","Region" }; + } + HRegion* hRegion() { return &m_hRegion; } + void setHRegion(HRegion const& _hregion) + { + if (!_hregion.IsInitialized()) + { + return; + } + m_hRegion = _hregion; + } + QSize getSize() + { + return m_size; + } + void setSize(QSize const& _size) + { + m_size = _size; + } +private: + HRegion m_hRegion; + QSize m_size; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionDifferenceModel.cpp b/NodeEditorPro/examples/halcon/HRegionDifferenceModel.cpp new file mode 100644 index 0000000..246dad2 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionDifferenceModel.cpp @@ -0,0 +1,153 @@ +#include "HRegionDifferenceModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionDifferenceModel::HRegionDifferenceModel() +{ + m_InRegion = std::make_shared(); + m_result = std::make_shared(); + m_InRegionDiv = std::make_shared(); + +} + +QString HRegionDifferenceModel::portCaption(PortType port, PortIndex port_index) const +{ + if (port == PortType::In) + { + switch (port_index) + { + case 0: + return "Ori"; + break; + case 1: + return "Div"; + break; + } + } + else if (port == PortType::Out) + { + switch (port_index) + { + case 0: + return "Res"; + break; + } + } + + return HRegionDifferenceModel::portCaption(port, port_index); +} + +bool HRegionDifferenceModel::RunTask() +{ + PortIndex const outPortIndex = 0; + if (m_InRegion->hRegion() == nullptr) + { + return false; + } + try + { + HalconCpp::Difference(*m_InRegion->hRegion(), *m_InRegionDiv->hRegion(), m_result->hRegion()); + + m_result->setSize(m_InRegion->getSize()); + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool HRegionDifferenceModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int HRegionDifferenceModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 2; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionDifferenceModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionDifferenceModel::validationMessage() const +{ + return modelValidationError; +} + +NodeDataType +HRegionDifferenceModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionDifferenceModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hRegionData = + std::dynamic_pointer_cast(data); + if (hRegionData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_InRegion->setHRegion(*hRegionData->hRegion()); + m_InRegion->setSize(hRegionData->getSize()); + break; + case 1: + m_InRegionDiv->setHRegion(*hRegionData->hRegion()); + m_InRegionDiv->setSize(hRegionData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionDifferenceModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_result); + break; + case 1: + break; + default: + break; + } + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionDifferenceModel.hpp b/NodeEditorPro/examples/halcon/HRegionDifferenceModel.hpp new file mode 100644 index 0000000..830c35a --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionDifferenceModel.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HRegionDifferenceModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionDifferenceModel(); + virtual ~HRegionDifferenceModel() {} + +public: + QString caption() const override + { + return QStringLiteral("相减区域"); + } + QString name() const override + { + return QStringLiteral("相减区域"); + } + virtual QString modelName() const + { + return QStringLiteral("相减区域"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return nullptr; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + bool portCaptionVisible(PortType, PortIndex) const override { return true; } + QString portCaption(PortType, PortIndex) const override; +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("区域输入未连接!"); +private: + std::shared_ptr m_InRegion; + std::shared_ptr m_InRegionDiv; + std::shared_ptr m_result; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionFillUpShapeModel.cpp b/NodeEditorPro/examples/halcon/HRegionFillUpShapeModel.cpp new file mode 100644 index 0000000..9d39aac --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionFillUpShapeModel.cpp @@ -0,0 +1,160 @@ +#include "HRegionFillUpShapeModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionFillUpShapeModel::HRegionFillUpShapeModel() +{ + m_hRegion = std::make_shared(); + m_result = std::make_shared(); + + m_widget = new QWidget(); + m_host = new QVBoxLayout(); + m_combo_feature = new QComboBox(); + + m_minvalEdit = new QLineEdit(); + m_maxvalEdit = new QLineEdit(); + m_widget->setContentsMargins(0, 0, 0, 0); + m_host->setSpacing(1); + m_widget->setLayout(m_host); + m_host->addWidget(m_combo_feature); + m_host->addWidget(m_minvalEdit); + m_host->addWidget(m_maxvalEdit); + m_widget->setFixedHeight(90); + m_widget->setFixedWidth(100); + m_combo_feature->addItem("area"); + m_combo_feature->addItem("compactness"); + m_combo_feature->addItem("convexity"); + m_combo_feature->addItem("anisometry"); + m_minvalEdit->setText("0.0"); + m_maxvalEdit->setText("100.0"); + + connect(m_minvalEdit, &QLineEdit::textChanged, [=]() + { + m_minval = m_minvalEdit->text().toDouble(); + RunTask(); + }); + connect(m_maxvalEdit, &QLineEdit::textChanged, [=]() + { + m_maxval = m_maxvalEdit->text().toDouble(); + RunTask(); + }); + connect(m_combo_feature, &QComboBox::currentTextChanged, [=]() + { + m_feature = m_combo_feature->currentText(); + RunTask(); + }); +} + +bool HRegionFillUpShapeModel::RunTask() +{ + Q_EMIT computingStarted(); + PortIndex const outPortIndex = 0; + try + { + HalconCpp::FillUpShape(*m_hRegion->hRegion(), m_result->hRegion(), + m_feature.toStdString().c_str(), + m_minval, + m_maxval); + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + Q_EMIT computingFinished(); + return true; +} + +unsigned int HRegionFillUpShapeModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionFillUpShapeModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionFillUpShapeModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HRegionFillUpShapeModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + + result.insert("m_minval", m_minval); + result.insert("m_maxval", m_maxval); + result.insert("m_feature", m_feature); + return result; +} + +void HRegionFillUpShapeModel::restore(QJsonObject const& json_values) +{ + NodeDataModel::restore(json_values); + m_minval = json_values.value("m_minval").toDouble(); + m_maxval = json_values.value("m_maxval").toDouble(); + m_feature = json_values.value("m_feature").toString(); + + m_combo_feature->setCurrentText(m_feature); + m_minvalEdit->setText(QString::number(m_minval)); + m_maxvalEdit->setText(QString::number(m_maxval)); +} + +NodeDataType +HRegionFillUpShapeModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionFillUpShapeModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hImageData = + std::dynamic_pointer_cast(data); + if (hImageData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_hRegion->setHRegion(*hImageData->hRegion()); + m_result->setSize(hImageData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionFillUpShapeModel:: +outData(PortIndex) +{ + return std::static_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionFillUpShapeModel.hpp b/NodeEditorPro/examples/halcon/HRegionFillUpShapeModel.hpp new file mode 100644 index 0000000..0971760 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionFillUpShapeModel.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HRegionFillUpShapeModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionFillUpShapeModel(); + virtual ~HRegionFillUpShapeModel() {} + +public: + QString caption() const override + { + return QStringLiteral("填充区域"); + } + QString name() const override + { + return QStringLiteral("填充区域"); + } + virtual QString modelName() const + { + return QStringLiteral("填充区域"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return m_widget; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + QJsonObject save() const override; + + void restore(QJsonObject const&) override; +protected: + bool RunTask(); + +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: + QString m_feature; + double m_minval = 0.0; + double m_maxval = 100.0; + std::shared_ptr m_hRegion; + std::shared_ptr m_result; + QWidget* m_widget; + QVBoxLayout* m_host; + QComboBox* m_combo_feature; + QLineEdit* m_minvalEdit; + QLineEdit* m_maxvalEdit; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionOpenCircleModel.cpp b/NodeEditorPro/examples/halcon/HRegionOpenCircleModel.cpp new file mode 100644 index 0000000..322b0d6 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionOpenCircleModel.cpp @@ -0,0 +1,126 @@ +#include "HRegionOpenCircleModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionOpenCircleModel::HRegionOpenCircleModel() +{ + m_hRegion = std::make_shared(); + m_result = std::make_shared(); + + m_maxvalEdit = new QLineEdit(); + m_maxvalEdit->setFixedWidth(80); + m_maxvalEdit->setText("9.0"); + + connect(m_maxvalEdit, &QLineEdit::textChanged, [=]() + { + m_maxval = m_maxvalEdit->text().toDouble(); + RunTask(); + }); +} + +bool HRegionOpenCircleModel::RunTask() +{ + Q_EMIT computingStarted(); + PortIndex const outPortIndex = 0; + try + { + HalconCpp::OpeningCircle( + *m_hRegion->hRegion(), + m_result->hRegion(), + m_maxval); + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + Q_EMIT computingFinished(); + return true; +} + +unsigned int HRegionOpenCircleModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionOpenCircleModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionOpenCircleModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HRegionOpenCircleModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + + result.insert("m_maxval", m_maxval); + return result; +} + +void HRegionOpenCircleModel::restore(QJsonObject const& json_values) +{ + NodeDataModel::restore(json_values); + m_maxval = json_values.value("m_maxval").toDouble(); + m_maxvalEdit->setText(QString::number(m_maxval)); +} + +NodeDataType +HRegionOpenCircleModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionOpenCircleModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hImageData = + std::dynamic_pointer_cast(data); + if (hImageData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_hRegion->setHRegion(*hImageData->hRegion()); + m_result->setSize(hImageData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionOpenCircleModel:: +outData(PortIndex) +{ + return std::static_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionOpenCircleModel.hpp b/NodeEditorPro/examples/halcon/HRegionOpenCircleModel.hpp new file mode 100644 index 0000000..234d9c0 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionOpenCircleModel.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HRegionOpenCircleModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionOpenCircleModel(); + virtual ~HRegionOpenCircleModel() {} + +public: + QString caption() const override + { + return QStringLiteral("开放圆"); + } + QString name() const override + { + return QStringLiteral("开放圆"); + } + virtual QString modelName() const + { + return QStringLiteral("开放圆"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return m_maxvalEdit; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + QJsonObject save() const override; + + void restore(QJsonObject const&) override; +protected: + bool RunTask(); + +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("图片输入未连接!"); +private: + double m_maxval = 100.0; + std::shared_ptr m_hRegion; + std::shared_ptr m_result; + QLineEdit* m_maxvalEdit; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionSelectModel.cpp b/NodeEditorPro/examples/halcon/HRegionSelectModel.cpp new file mode 100644 index 0000000..1a8876a --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionSelectModel.cpp @@ -0,0 +1,192 @@ +#include "HRegionSelectModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionSelectModel::HRegionSelectModel() +{ + m_widget = new QWidget(); + m_widget->setAttribute(Qt::WA_NoSystemBackground); + m_widget->setFixedSize(150, 140); + m_minValue = new QLineEdit(m_widget); + m_maxValue = new QLineEdit(m_widget); + combo_feature = new QComboBox(m_widget); + combo_operation = new QComboBox(m_widget); + m_minValue->resize(120, 25); + m_maxValue->resize(120, 25); + combo_feature->move(0, 0); + combo_operation->move(0, 35); + m_minValue->move(0, 70); + m_maxValue->move(0, 105); + + m_minValue->setText("0"); + m_maxValue->setText("99999"); + + combo_feature->addItem("area"); + combo_feature->addItem("row"); + combo_feature->addItem("column"); + combo_feature->addItem("width"); + combo_feature->addItem("height"); + combo_feature->addItem("circularity"); + combo_feature->addItem("compactness"); + combo_feature->addItem("contlength"); + combo_feature->addItem("convexity"); + combo_feature->addItem("rectangularity"); + combo_feature->addItem("inner_width"); + combo_feature->addItem("inner_height"); + combo_feature->addItem("roundness"); + + combo_operation->addItem("and"); + combo_operation->addItem("or"); + + m_InRegion = std::make_shared(); + m_result = std::make_shared(); + + connect(m_minValue, &QLineEdit::textEdited, [=]() + { + RunTask(); + }); + connect(m_maxValue, &QLineEdit::textEdited, [=]() + { + RunTask(); + }); + connect(combo_feature, &QComboBox::currentTextChanged, [=]() + { + RunTask(); + }); + connect(combo_operation, &QComboBox::currentTextChanged, [=]() + { + RunTask(); + }); +} + +bool HRegionSelectModel::RunTask() +{ + PortIndex const outPortIndex = 0; + if (m_InRegion->hRegion() == nullptr) + { + return false; + } + try + { + minVal = m_minValue->text().toDouble(); + maxVal = m_maxValue->text().toDouble(); + m_cur_feature = combo_feature->currentText(); + m_cur_operation = combo_operation->currentText(); + m_result->setHRegion(m_InRegion->hRegion()->SelectShape( + combo_feature->currentText().toStdString().c_str(), + combo_operation->currentText().toStdString().c_str(), + minVal, maxVal + )); + m_result->setSize(m_InRegion->getSize()); + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool HRegionSelectModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int HRegionSelectModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionSelectModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionSelectModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HRegionSelectModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + result.insert("m_minValue", m_minValue->text()); + result.insert("m_maxValue", m_maxValue->text()); + return result; +} + +void HRegionSelectModel::restore(QJsonObject const& p) +{ + m_minValue->setText(p["m_minValue"].toString("0")); + m_maxValue->setText(p["m_maxValue"].toString("99999")); +} + +NodeDataType +HRegionSelectModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionSelectModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hRegionData = + std::dynamic_pointer_cast(data); + if (hRegionData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_InRegion->setHRegion(*hRegionData->hRegion()); + m_InRegion->setSize(hRegionData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionSelectModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_result); + break; + case 1: + break; + default: + break; + } + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionSelectModel.hpp b/NodeEditorPro/examples/halcon/HRegionSelectModel.hpp new file mode 100644 index 0000000..8b43481 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionSelectModel.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HRegionSelectModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionSelectModel(); + virtual ~HRegionSelectModel() {} + +public: + QString caption() const override + { + return QStringLiteral("选择区域"); + } + QString name() const override + { + return QStringLiteral("选择区域"); + } + virtual QString modelName() const + { + return QStringLiteral("选择区域"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return m_widget; } + + bool + resizable() const override { return true; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + + QJsonObject save() const override; + + void restore(QJsonObject const&) override; + +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("区域输入未连接!"); +private: + std::shared_ptr m_InRegion; + std::shared_ptr m_result; + QComboBox* combo_feature; + QComboBox* combo_operation; + QLineEdit* m_minValue; + QLineEdit* m_maxValue; + QWidget* m_widget; + QString m_cur_feature; + QString m_cur_operation; + int minVal; + int maxVal; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionSelectShapeStdModel.cpp b/NodeEditorPro/examples/halcon/HRegionSelectShapeStdModel.cpp new file mode 100644 index 0000000..bf8ece1 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionSelectShapeStdModel.cpp @@ -0,0 +1,161 @@ +#include "HRegionSelectShapeStdModel.hpp" +#include +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionSelectShapeStdModel::HRegionSelectShapeStdModel() +{ + m_widget = new QWidget(); + m_widget->setAttribute(Qt::WA_NoSystemBackground); + m_widget->setFixedSize(120, 80); + m_cur_feature = "rectangle1"; + m_percent = 90; + combo_feature = new QComboBox(m_widget); + m_percentEdit = new QLineEdit(m_widget); + combo_feature->move(0, 10); + m_percentEdit->move(0, 60); + combo_feature->addItem("max_area"); + combo_feature->addItem("rectangle1"); + combo_feature->addItem("rectangle2"); + m_percentEdit->setText("90.0"); + m_InRegion = std::make_shared(); + m_result = std::make_shared(); + + connect(combo_feature, &QComboBox::currentTextChanged, [=]() + { + m_cur_feature = combo_feature->currentText(); + RunTask(); + }); + connect(m_percentEdit, &QLineEdit::textEdited, this, [=]() + { + m_percent = m_percentEdit->text().toDouble(); + RunTask(); + }); +} + +bool HRegionSelectShapeStdModel::RunTask() +{ + PortIndex const outPortIndex = 0; + if (m_InRegion->hRegion() == nullptr) + { + return false; + } + try + { + HalconCpp::SelectShapeStd( + *m_InRegion->hRegion(), + m_result->hRegion(), + m_cur_feature.toStdString().c_str(), + m_percent); + + m_result->setSize(m_InRegion->getSize()); + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool HRegionSelectShapeStdModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int HRegionSelectShapeStdModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionSelectShapeStdModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionSelectShapeStdModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HRegionSelectShapeStdModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + result.insert("m_cur_feature", combo_feature->currentText()); + result.insert("m_percent", m_percent); + return result; +} + +void HRegionSelectShapeStdModel::restore(QJsonObject const& p) +{ + combo_feature->setCurrentText(p["m_cur_feature"].toString("rectangle1")); + m_percent = p.value("m_percent").toDouble(90); +} + +NodeDataType +HRegionSelectShapeStdModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionSelectShapeStdModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hRegionData = + std::dynamic_pointer_cast(data); + if (hRegionData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_InRegion->setHRegion(*hRegionData->hRegion()); + m_InRegion->setSize(hRegionData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionSelectShapeStdModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_result); + break; + case 1: + break; + default: + break; + } + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionSelectShapeStdModel.hpp b/NodeEditorPro/examples/halcon/HRegionSelectShapeStdModel.hpp new file mode 100644 index 0000000..4cb43f7 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionSelectShapeStdModel.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon shapeTrans节点 + */ +class HRegionSelectShapeStdModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionSelectShapeStdModel(); + virtual ~HRegionSelectShapeStdModel() {} + +public: + QString caption() const override + { + return QStringLiteral("选择形状"); + } + QString name() const override + { + return QStringLiteral("选择形状"); + } + virtual QString modelName() const + { + return QStringLiteral("选择形状"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return m_widget; } + + bool + resizable() const override { return false; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + + QJsonObject save() const override; + + void restore(QJsonObject const&) override; + +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("区域输入未连接!"); +private: + std::shared_ptr m_InRegion; + std::shared_ptr m_result; + QLineEdit* m_percentEdit; + QComboBox* combo_feature; + QWidget* m_widget; + QString m_cur_feature; + double m_percent; +}; diff --git a/NodeEditorPro/examples/halcon/HRegionShapeTransModel.cpp b/NodeEditorPro/examples/halcon/HRegionShapeTransModel.cpp new file mode 100644 index 0000000..f766d46 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionShapeTransModel.cpp @@ -0,0 +1,154 @@ +#include "HRegionShapeTransModel.hpp" +#include +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionShapeTransModel::HRegionShapeTransModel() +{ + m_widget = new QWidget(); + m_widget->setAttribute(Qt::WA_NoSystemBackground); + m_widget->setFixedSize(150, 50); + combo_feature = new QComboBox(m_widget); + + combo_feature->move(0, 10); + + combo_feature->addItem("convex"); + combo_feature->addItem("ellipse"); + combo_feature->addItem("outer_circle"); + combo_feature->addItem("inner_circle"); + combo_feature->addItem("rectangle1"); + combo_feature->addItem("rectangle2"); + combo_feature->addItem("inner_rectangle1"); + combo_feature->addItem("inner_rectangle2"); + + m_InRegion = std::make_shared(); + m_result = std::make_shared(); + + connect(combo_feature, &QComboBox::currentTextChanged, [=]() + { + RunTask(); + }); +} + +bool HRegionShapeTransModel::RunTask() +{ + PortIndex const outPortIndex = 0; + if (m_InRegion->hRegion() == nullptr) + { + return false; + } + try + { + m_cur_feature = combo_feature->currentText(); + m_result->setHRegion(m_InRegion->hRegion()->ShapeTrans( + combo_feature->currentText().toStdString().c_str() + )); + m_result->setSize(m_InRegion->getSize()); + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool HRegionShapeTransModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int HRegionShapeTransModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionShapeTransModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionShapeTransModel::validationMessage() const +{ + return modelValidationError; +} + +QJsonObject HRegionShapeTransModel::save() const +{ + QJsonObject result = NodeDataModel::save(); + result.insert("m_cur_feature", combo_feature->currentText()); + return result; +} + +void HRegionShapeTransModel::restore(QJsonObject const& p) +{ + combo_feature->setCurrentText(p["m_cur_feature"].toString("rectangle1")); +} + +NodeDataType +HRegionShapeTransModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionShapeTransModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hRegionData = + std::dynamic_pointer_cast(data); + if (hRegionData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_InRegion->setHRegion(*hRegionData->hRegion()); + m_InRegion->setSize(hRegionData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionShapeTransModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_result); + break; + case 1: + break; + default: + break; + } + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionShapeTransModel.hpp b/NodeEditorPro/examples/halcon/HRegionShapeTransModel.hpp new file mode 100644 index 0000000..6f853fb --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionShapeTransModel.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon shapeTrans节点 + */ +class HRegionShapeTransModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionShapeTransModel(); + virtual ~HRegionShapeTransModel() {} + +public: + QString caption() const override + { + return QStringLiteral("仿形变换"); + } + QString name() const override + { + return QStringLiteral("仿形变换"); + } + virtual QString modelName() const + { + return QStringLiteral("仿形变换"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return m_widget; } + + bool + resizable() const override { return true; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + + QJsonObject save() const override; + + void restore(QJsonObject const&) override; + +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("区域输入未连接!"); +private: + std::shared_ptr m_InRegion; + std::shared_ptr m_result; + QComboBox* combo_feature; + QWidget* m_widget; + QString m_cur_feature; + +}; diff --git a/NodeEditorPro/examples/halcon/HRegionUnionModel.cpp b/NodeEditorPro/examples/halcon/HRegionUnionModel.cpp new file mode 100644 index 0000000..e9ff693 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionUnionModel.cpp @@ -0,0 +1,152 @@ +#include "HRegionUnionModel.hpp" +#include + +#include "halconcpp/HalconCpp.h" + +using namespace HalconCpp; + +HRegionUnionModel::HRegionUnionModel() +{ + m_InRegion = std::make_shared(); + m_result = std::make_shared(); + +} + +QString HRegionUnionModel::portCaption(PortType port, PortIndex port_index) const +{ + if (port == PortType::In) + { + switch (port_index) + { + case 0: + return "Ori"; + break; + case 1: + return "Add"; + break; + } + } + else if (port == PortType::Out) + { + switch (port_index) + { + case 0: + return "Res"; + break; + } + } + + return NodeDataModel::portCaption(port, port_index); +} + +bool HRegionUnionModel::RunTask() +{ + PortIndex const outPortIndex = 0; + if (m_InRegion->hRegion() == nullptr) + { + return false; + } + try + { + HalconCpp::Union2(*m_InRegion->hRegion(), *m_InRegionAdd->hRegion(), m_result->hRegion()); + + m_result->setSize(m_InRegion->getSize()); + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("缺失或运行失败!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool HRegionUnionModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int HRegionUnionModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 2; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeValidationState HRegionUnionModel::validationState() const +{ + return modelValidationState; +} + +QString HRegionUnionModel::validationMessage() const +{ + return modelValidationError; +} + +NodeDataType +HRegionUnionModel::dataType(PortType, PortIndex) const +{ + return HRegionData().type(); +} + +void HRegionUnionModel:: +setInData(std::shared_ptr data, int portIndex) +{ + auto hRegionData = + std::dynamic_pointer_cast(data); + if (hRegionData == nullptr) + { + return; + } + switch (portIndex) + { + case 0: + m_InRegion->setHRegion(*hRegionData->hRegion()); + m_InRegion->setSize(hRegionData->getSize()); + break; + case 1: + m_InRegionAdd->setHRegion(*hRegionData->hRegion()); + m_InRegionAdd->setSize(hRegionData->getSize()); + break; + default: + break; + } + RunTask(); +} + +std::shared_ptr +HRegionUnionModel:: +outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(m_result); + break; + case 1: + break; + default: + break; + } + return std::dynamic_pointer_cast(m_result); +} diff --git a/NodeEditorPro/examples/halcon/HRegionUnionModel.hpp b/NodeEditorPro/examples/halcon/HRegionUnionModel.hpp new file mode 100644 index 0000000..3965c94 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HRegionUnionModel.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "halconcpp/HalconCpp.h" +#include "HImageData.hpp" +#include "HRegionData.hpp" +#include "HImageViewWidget.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using namespace HalconCpp; +/** + * \brief halcon 图像rgb2gray节点 + */ +class HRegionUnionModel :public NodeDataModel +{ + Q_OBJECT +public: + HRegionUnionModel(); + virtual ~HRegionUnionModel() {} + +public: + QString caption() const override + { + return QStringLiteral("联合区域"); + } + QString name() const override + { + return QStringLiteral("联合区域"); + } + virtual QString modelName() const + { + return QStringLiteral("联合区域"); + } + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override; + + QWidget* + embeddedWidget() override { return nullptr; } + + bool + resizable() const override { return true; } + NodeValidationState + validationState() const override; + QString + validationMessage() const override; + bool portCaptionVisible(PortType, PortIndex) const override { return true; } + QString portCaption(PortType, PortIndex) const override; +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("区域输入未连接!"); +private: + std::shared_ptr m_InRegion; + std::shared_ptr m_InRegionAdd; + std::shared_ptr m_result; +}; diff --git a/NodeEditorPro/examples/halcon/HalconNodes.hpp b/NodeEditorPro/examples/halcon/HalconNodes.hpp new file mode 100644 index 0000000..a064b37 --- /dev/null +++ b/NodeEditorPro/examples/halcon/HalconNodes.hpp @@ -0,0 +1,19 @@ +#pragma once + +//halcon nodes +#include "halcon/HImageLoaderModel.hpp" +#include "halcon/HImageFolderModel.hpp" +#include "halcon/HImageShowModel.hpp" +#include "halcon/HImageRGB2GrayModel.hpp" +#include "halcon/HImageThresholdModel.hpp" +#include "halcon/HImageSplitChanelModel.hpp" +#include "halcon/HRegionSelectModel.hpp" +#include "halcon/HRegionConnectModel.hpp" +#include "halcon/HRegionShapeTransModel.hpp" +#include "halcon/HImageReduceDomainModel.hpp" +#include "halcon/HRegionFillUpShapeModel.hpp" +#include "halcon/HRegionOpenCircleModel.hpp" +#include "halcon/HRegionUnionModel.hpp" +#include "halcon/HRegionDifferenceModel.hpp" +#include "halcon/HRegionSelectShapeStdModel.hpp" +#include "halcon/HImageDLSegmentModel.hpp" diff --git a/NodeEditorPro/examples/images/ImageLoaderModel.cpp b/NodeEditorPro/examples/images/ImageLoaderModel.cpp new file mode 100644 index 0000000..76dff52 --- /dev/null +++ b/NodeEditorPro/examples/images/ImageLoaderModel.cpp @@ -0,0 +1,97 @@ +#include "ImageLoaderModel.hpp" + +#include +#include +#include + +ImageLoaderModel:: +ImageLoaderModel() + : _label(new QLabel(QStringLiteral("点击加载图片!"))) +{ + //_label->setAlignment(Qt::AlignHCenter); + + QFont f = _label->font(); + f.setBold(true); + f.setItalic(true); + + _label->setFont(f); + + _label->setFixedSize(200, 200); + + _label->installEventFilter(this); +} + + +unsigned int +ImageLoaderModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 0; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +bool +ImageLoaderModel:: +eventFilter(QObject* object, QEvent* event) +{ + if (object == _label) + { + int w = _label->width(); + int h = _label->height(); + + if (event->type() == QEvent::MouseButtonPress) + { + + QString fileName = + QFileDialog::getOpenFileName(nullptr, + tr("Open Image"), + QDir::homePath(), + tr("Image Files (*.png *.jpg *.bmp)")); + + _pixmap = QPixmap(fileName); + + _label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio)); + + Q_EMIT dataUpdated(0); + + return true; + } + else if (event->type() == QEvent::Resize) + { + if (!_pixmap.isNull()) + _label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio)); + } + } + + return false; +} + + +NodeDataType +ImageLoaderModel:: +dataType(PortType, PortIndex) const +{ + return PixmapData().type(); +} + + +std::shared_ptr +ImageLoaderModel:: +outData(PortIndex) +{ + return std::make_shared(_pixmap); +} diff --git a/NodeEditorPro/examples/images/ImageLoaderModel.hpp b/NodeEditorPro/examples/images/ImageLoaderModel.hpp new file mode 100644 index 0000000..cb05ad3 --- /dev/null +++ b/NodeEditorPro/examples/images/ImageLoaderModel.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include + +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" + +#include "PixmapData.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class ImageLoaderModel : public NodeDataModel +{ + Q_OBJECT + +public: + ImageLoaderModel(); + + virtual + ~ImageLoaderModel() {} + +public: + + QString + caption() const override + { + return QStringLiteral("QPixmap输入"); + } + + QString + name() const override { return QStringLiteral("QPixmap输入"); } + +public: + + virtual QString + modelName() const + { + return QStringLiteral("QPixmap输入"); + } + + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr, int) override + { } + + QWidget* + embeddedWidget() override { return _label; } + + bool + resizable() const override { return true; } + +protected: + + bool + eventFilter(QObject* object, QEvent* event) override; + +private: + + QLabel* _label; + + QPixmap _pixmap; +}; diff --git a/NodeEditorPro/examples/images/ImageShowModel.cpp b/NodeEditorPro/examples/images/ImageShowModel.cpp new file mode 100644 index 0000000..0dd8dcc --- /dev/null +++ b/NodeEditorPro/examples/images/ImageShowModel.cpp @@ -0,0 +1,110 @@ +#include "ImageShowModel.hpp" + +#include +#include + +#include + +#include "DataModelRegistry.hpp" + +#include "PixmapData.hpp" + +ImageShowModel:: +ImageShowModel() + : _label(new QLabel("Image will appear here")) +{ + _label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + + QFont f = _label->font(); + f.setBold(true); + f.setItalic(true); + + _label->setFont(f); + + _label->setFixedSize(200, 200); + + _label->installEventFilter(this); +} + +unsigned int +ImageShowModel:: +nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + + +bool +ImageShowModel:: +eventFilter(QObject* object, QEvent* event) +{ + if (object == _label) + { + int w = _label->width(); + int h = _label->height(); + + if (event->type() == QEvent::Resize) + { + auto d = std::dynamic_pointer_cast(_nodeData); + if (d) + { + _label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); + } + } + } + return false; +} + +NodeDataType +ImageShowModel:: +dataType(PortType, PortIndex) const +{ + return PixmapData().type(); +} + + +std::shared_ptr +ImageShowModel:: +outData(PortIndex) +{ + return _nodeData; +} + + +void +ImageShowModel:: +setInData(std::shared_ptr nodeData, PortIndex) +{ + _nodeData = nodeData; + + if (_nodeData) + { + auto d = std::dynamic_pointer_cast(_nodeData); + + int w = _label->width(); + int h = _label->height(); + + _label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); + } + else + { + _label->setPixmap(QPixmap()); + } + + Q_EMIT dataUpdated(0); +} diff --git a/NodeEditorPro/examples/images/ImageShowModel.hpp b/NodeEditorPro/examples/images/ImageShowModel.hpp new file mode 100644 index 0000000..e3a4434 --- /dev/null +++ b/NodeEditorPro/examples/images/ImageShowModel.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include + +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class ImageShowModel : public NodeDataModel +{ + Q_OBJECT + +public: + ImageShowModel(); + + virtual + ~ImageShowModel() {} + +public: + + QString + caption() const override + { + return QStringLiteral("QPixmap 显示"); + } + + QString + name() const override + { + return QStringLiteral("QPixmap显示节点"); + } + +public: + + virtual QString + modelName() const + { + return QStringLiteral("QPixmap显示"); + } + + unsigned int + nPorts(PortType portType) const override; + + NodeDataType + dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr + outData(PortIndex port) override; + + void + setInData(std::shared_ptr nodeData, PortIndex port) override; + + QWidget* + embeddedWidget() override { return _label; } + + bool + resizable() const override { return true; } + +protected: + + bool + eventFilter(QObject* object, QEvent* event) override; + +private: + + QLabel* _label; + + std::shared_ptr _nodeData; +}; diff --git a/NodeEditorPro/examples/images/PixmapData.hpp b/NodeEditorPro/examples/images/PixmapData.hpp new file mode 100644 index 0000000..dd62e81 --- /dev/null +++ b/NodeEditorPro/examples/images/PixmapData.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" + +using QtNodes::NodeData; +using QtNodes::NodeDataType; + +/// The class can potentially incapsulate any user data which +/// need to be transferred within the Node Editor graph +class PixmapData : public NodeData +{ +public: + + PixmapData() {} + + PixmapData(QPixmap const& pixmap) + : _pixmap(pixmap) + {} + + NodeDataType + type() const override + { + // id name + return { "pixmap", "QPixmap" }; + } + + QPixmap + pixmap() const { return _pixmap; } + +private: + + QPixmap _pixmap{}; +}; diff --git a/NodeEditorPro/examples/images/VisionFlowWidget.cpp b/NodeEditorPro/examples/images/VisionFlowWidget.cpp new file mode 100644 index 0000000..3cd6108 --- /dev/null +++ b/NodeEditorPro/examples/images/VisionFlowWidget.cpp @@ -0,0 +1,55 @@ +#include "VisionFlowWidget.hpp" + +VisionFlowWidget::VisionFlowWidget(QWidget* parent) +{ + main_layout = new QVBoxLayout(); + header_layout = new QHBoxLayout(); + btn_load_scheme = new QPushButton("Load"); + btn_save_scheme = new QPushButton("Save"); + btn_clear_scene = new QPushButton("Clear"); + btn_test = new QPushButton("test"); + header_layout->setAlignment(Qt::AlignLeft); + btn_load_scheme->setFixedWidth(120); + btn_save_scheme->setFixedWidth(120); + btn_clear_scene->setFixedWidth(120); + this->setLayout(main_layout); + main_layout->addLayout(header_layout); + header_layout->addWidget(btn_load_scheme); + header_layout->addWidget(btn_save_scheme); + header_layout->addWidget(btn_clear_scene); + header_layout->addWidget(btn_test); + header_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setContentsMargins(1, 1, 1, 1); + main_layout->setSpacing(1); + m_scene = new FlowScene(registerDataModels()); + m_view = new FlowView(m_scene); + main_layout->addWidget(m_view); + this->setWindowTitle(QStringLiteral("节点编辑器")); + this->resize(1280, 720); + setConnection(); +} + +VisionFlowWidget::~VisionFlowWidget() +{ + +} + +void VisionFlowWidget::setConnection() +{ + connect(btn_load_scheme, &QPushButton::clicked, [=]() + { + m_scene->load(); + }); + connect(btn_save_scheme, &QPushButton::clicked, [=]() + { + m_scene->save(); + }); + connect(btn_clear_scene, &QPushButton::clicked, [=]() + { + m_scene->clearScene(); + }); + connect(btn_test, &QPushButton::clicked, [=]() + { + DrawShapeView::getInst()->show(); + }); +} diff --git a/NodeEditorPro/examples/images/VisionFlowWidget.hpp b/NodeEditorPro/examples/images/VisionFlowWidget.hpp new file mode 100644 index 0000000..8926b6f --- /dev/null +++ b/NodeEditorPro/examples/images/VisionFlowWidget.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "NodeData.hpp" +#include "FlowScene.hpp" +#include "FlowView.hpp" +#include +#include +#include +#include +#include + +#include "ImageShowModel.hpp" +#include "ImageLoaderModel.hpp" +#include "DrawShapeView.hpp" +#include "halcon/HalconNodes.hpp" +#include "calculator/MathNodes.hpp" + +//#include "opcv/CvImageLoaderModel.h" +//#include "opcv/CvImageShowModel.h" +#include "opcv/MoudleOpencvNodes.h" + +using QtNodes::DataModelRegistry; +using QtNodes::FlowScene; +using QtNodes::FlowView; + + +class VisionFlowWidget :public QWidget +{ +public: + VisionFlowWidget(QWidget* parent = Q_NULLPTR); + virtual ~VisionFlowWidget(); +private: + QVBoxLayout* main_layout; + QHBoxLayout* header_layout; + QPushButton* btn_load_scheme; + QPushButton* btn_save_scheme; + QPushButton* btn_clear_scene; + QPushButton* btn_test; + FlowScene* m_scene; + FlowView* m_view; + //DrawShapeView* m_draw_shape_view; +private: + void setConnection(); +}; + +static std::shared_ptr registerDataModels() +{ + QString numberNodeType = QStringLiteral("数学操作"); + QString halconImageNodeType = QStringLiteral("Halcon图像操作"); + QString getHalconImageNodeType = QStringLiteral("获取Halcon图像"); + QString dlNodeType = QStringLiteral("深度学习"); + QString matchNodeType = QStringLiteral("模板匹配"); + + QString getOpencvImageNodeType = QStringLiteral("00.获取OpenCV图像"); + QString opencvImageNodeType = QStringLiteral("01.OpenCV图像操作"); + + auto ret = std::make_shared(); + + //opencv图像获取 + ret->registerModel(getOpencvImageNodeType); + + //opencv图像操作 + ret->registerModel(opencvImageNodeType); + ret->registerModel(opencvImageNodeType); + ret->registerModel(opencvImageNodeType); + + //数学节点 + ret->registerModel(numberNodeType); + ret->registerModel(numberNodeType); + ret->registerModel(numberNodeType); + ret->registerModel(numberNodeType); + ret->registerModel(numberNodeType); + ret->registerModel(numberNodeType); + + //图像获取 + ret->registerModel(getHalconImageNodeType); + //ret->registerModel(getHalconImageNodeType); + //图像操作 + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + //ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + ret->registerModel(halconImageNodeType); + + return ret; +} diff --git a/NodeEditorPro/examples/images/main.cpp b/NodeEditorPro/examples/images/main.cpp new file mode 100644 index 0000000..2bf8600 --- /dev/null +++ b/NodeEditorPro/examples/images/main.cpp @@ -0,0 +1,22 @@ + +#include +#include +#include "VisionFlowWidget.hpp" + +#include "QBreakpadHandler.h" + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + app.setWindowIcon(QIcon(":/logo.png")); + QStyle* style = QStyleFactory::create("Fusion"); + app.setStyle(style); + + VisionFlowWidget* mainWidget = new VisionFlowWidget(); + mainWidget->show(); + + QBreakpadInstance.setDumpPath("crash"); + + return app.exec(); +} + diff --git a/NodeEditorPro/examples/opcv/CvAlgorithmTools.cpp b/NodeEditorPro/examples/opcv/CvAlgorithmTools.cpp new file mode 100644 index 0000000..5951a22 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvAlgorithmTools.cpp @@ -0,0 +1,27 @@ +#include "opcv/CvAlgorithmTools.h" + +CvAlgorithmTools::CvAlgorithmTools(QObject* parent) + :QObject(parent) +{ + +} + +CvAlgorithmTools::~CvAlgorithmTools() {} + +void CvAlgorithmTools::CvImageRgb2Gray(cv::Mat rgbImg) +{ + if (rgbImg.empty()) + return; + cv::Mat src, dst; + rgbImg.copyTo(src); + if (src.channels() == 3) + cv::cvtColor(src, dst, CV_BGR2GRAY); + else if (src.channels() == 1) + dst = src; + //qDebug() << ""; + QThread::sleep(2); + qDebug() << "CvAlgorithmTools::CvImageRgb2Gray thread:" << QThread::currentThreadId(); + + emit sendCvImageRgb2GrayResult(dst); +} + diff --git a/NodeEditorPro/examples/opcv/CvAlgorithmTools.h b/NodeEditorPro/examples/opcv/CvAlgorithmTools.h new file mode 100644 index 0000000..1ca14b6 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvAlgorithmTools.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include +#include + +class CvAlgorithmTools : public QObject +{ + Q_OBJECT +public: + CvAlgorithmTools(QObject* parent = Q_NULLPTR); + ~CvAlgorithmTools(); + +public: + void CvImageRgb2Gray(cv::Mat rgbImg); + + + +signals: + //CvImageRgb2Grayؽ + void sendCvImageRgb2GrayResult(cv::Mat grayImg); + + + +}; \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvGraphicsShowModel.cpp b/NodeEditorPro/examples/opcv/CvGraphicsShowModel.cpp new file mode 100644 index 0000000..1452d7e --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvGraphicsShowModel.cpp @@ -0,0 +1,222 @@ +#include "opcv/CvGraphicsShowModel.h" + + +CvGraphicsShowModel::CvGraphicsShowModel() +{ + //mCvImageView = new CvImageViewWidget(); + //mCvImageView->installEventFilter(this); + //mCvImageView->resize(600, 450); + //moveToThread(this); + + mCvGraphicsView = new CvGraphicsViewWidget(); + mCvGraphicsView->setObjectName(QStringLiteral("graphicsView")); + mCvGraphicsView->setStyleSheet( + "QGraphicsView#graphicsView{ " + "background-color:transparent; " + "border: 1px solid #F0F2F4; " + "border-radius: 4px; }"); + + widget = new QWidget(); + widget->resize(600, 450); + widget->setObjectName(QStringLiteral("widget")); + widget->setStyleSheet("QWidget#widget { background-color:transparent; }"); + + QGridLayout* gridLayout = new QGridLayout(widget); + QVBoxLayout* verticalLayout = new QVBoxLayout(); + QHBoxLayout* horizontalLayout = new QHBoxLayout(); + horizontalLayout->setDirection(QHBoxLayout::LeftToRight); + + QLabel* label = new QLabel(); + label->setObjectName(QStringLiteral("label")); + label->setText(QStringLiteral("ͼƬ:")); + label->setStyleSheet("QLabel#label { color:#FFFFFF; }"); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(label->sizePolicy().hasHeightForWidth()); + label->setSizePolicy(sizePolicy); + label->setMaximumSize(QSize(60, 16777215)); + + horizontalSlider = new QSlider(); + horizontalSlider->setObjectName(QString::fromUtf8("horizontalSlider")); + horizontalSlider->setMinimum(-14); + horizontalSlider->setMaximum(14); + horizontalSlider->setPageStep(2); + horizontalSlider->setOrientation(Qt::Horizontal); + horizontalSlider->setTickPosition(QSlider::TicksBelow); + QObject::connect(horizontalSlider, &QSlider::valueChanged, this, &CvGraphicsShowModel::onQSliderValueChanged); + + QSpacerItem* horizontalSpacer = new QSpacerItem(15, 20, QSizePolicy::Fixed, QSizePolicy::Minimum); + horizontalLayout->addItem(horizontalSpacer); + + horizontalLayout->addWidget(label); + horizontalLayout->addWidget(horizontalSlider); + + horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout->addItem(horizontalSpacer); + + verticalLayout->addLayout(horizontalLayout); + verticalLayout->addWidget(mCvGraphicsView); + + gridLayout->addLayout(verticalLayout, 0, 0, 1, 1); + + mCvImageData = std::make_shared(); +} + +void CvGraphicsShowModel::onQSliderValueChanged(int val) +{ + qDebug() << "onQSliderValueChanged:" << val; + qreal scale = 1.0; + if (val > 0) + { + scale = qPow(1.2, val); + qreal temp = scale; + scale = scale / mScaledNum; + mScaledNum = temp;//±ϵ + } + else if (val < 0) + { + scale = qPow(1.2, val); + qreal temp = scale; + scale = scale / mScaledNum; + mScaledNum = temp;//±ϵ + } + else if (val == 0) + { + qreal temp = scale; + scale = scale / mScaledNum; + mScaledNum = temp;//±ϵ + } + mCvGraphicsView->scale(scale, scale); +} + +void CvGraphicsShowModel::inputConnectionDeleted(QtNodes::Connection const&) +{ + mCvGraphicsView->setScene(new QGraphicsScene()); + + mCvImageData = std::make_shared(); + + PortIndex const outPortIndex = 0; + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("ͼƬδ!"); + Q_EMIT dataUpdated(outPortIndex); +} + +unsigned int CvGraphicsShowModel::nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeDataType CvGraphicsShowModel::dataType(PortType portType, PortIndex portIndex) const +{ + switch (portIndex) + { + case 0: + return CvImageData().type(); + break; + case 1: + return CvImageData().type(); + break; + } + return CvImageData().type(); +} + +bool CvGraphicsShowModel::RunTask() +{ + PortIndex const outPortIndex = 0; + try + { + //qDebug() << "11show"; + //QThread::sleep(3); + //QThread* thread = new QThread(); + //qDebug() << "CvGraphicsShowModel::RunTask thread:" << thread->currentThreadId(); + + mCvGraphicsView->showImage(*mCvImageData->CvImage()); + mCvGraphicsView->scale(0.85, 0.85); + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("ȱʧʧ!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +void CvGraphicsShowModel::setInData(std::shared_ptrdata, int) +{ + if (data == nullptr) + return; + if (data->type() == mCvImageData->type()) + { + auto dataPtr = std::dynamic_pointer_cast(data); + if (dataPtr->CvImage()->empty()) + return; + mCvImageData->setCvImage(*dataPtr->CvImage()); + } + RunTask(); +} + +bool CvGraphicsShowModel::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::Wheel) + { + + } + //if (object == mCvGraphicsView->viewport()) + //{ + // qDebug() << event->type(); + // if (event->type() == QEvent::Wheel || event->type() == QEvent::GraphicsSceneWheel) + // { + // QWheelEvent* wheelEvent = static_cast(event); + // qreal qrTmp = 1.0; + // if (mScaledFactor < 0.01 || mScaledFactor > 2000) + // return false; + // if (wheelEvent->delta() > 0) + // { + // qrTmp = 1.2; + // mCvGraphicsView->scale(qrTmp, qrTmp); + // } + // else + // { + // qrTmp = 1.0 / 1.2; + // mCvGraphicsView->scale(qrTmp, qrTmp); + // } + // mScaledFactor = mScaledFactor * qrTmp; //Ŵ + // qDebug() << "GraphicsSceneWheel"; + // return true; + // } + //} + return false; +} + +std::shared_ptr CvGraphicsShowModel::outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(mCvImageData); + break; + } + return std::dynamic_pointer_cast(mCvImageData); +} + diff --git a/NodeEditorPro/examples/opcv/CvGraphicsShowModel.h b/NodeEditorPro/examples/opcv/CvGraphicsShowModel.h new file mode 100644 index 0000000..ceeeb76 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvGraphicsShowModel.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +//#include +#include +#include + +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" +#include "Connection.hpp" + +#include "opcv/CvImageData.h" +#include "opcv/CvGraphicsViewWidget.h" + +#include + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; +using QtNodes::Connection; + +class CvGraphicsShowModel :public NodeDataModel +{ + Q_OBJECT + +public: + CvGraphicsShowModel(); + virtual ~CvGraphicsShowModel() {} + +public: + QString caption() const override { return QStringLiteral("cvͼGraphicsʾ"); } + QString name() const override { return QStringLiteral("cvͼGraphicsʾ"); } + virtual QString modelName() const { return QStringLiteral("cvͼGraphicsʾ"); } + + QWidget* embeddedWidget() override { return widget; } + //bool resizable() const override { return true; } + bool resizable() const override { return false; } + + NodeValidationState validationState() const override { return modelValidationState; } + QString validationMessage() const override { return modelValidationError; } + + unsigned int nPorts(PortType portType) const override; + NodeDataType dataType(PortType portType, PortIndex portIndex) const override; + + +public Q_SLOTS: + void onQSliderValueChanged(int val); + + //void onInputConnectionDeleted(); + + void inputConnectionDeleted(QtNodes::Connection const&) override; + +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; + +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("ͼƬδ!"); + +private: + qreal mScaledNum = 1.0; //ӿű + + QSlider* horizontalSlider; + QWidget* widget; + CvGraphicsViewWidget* mCvGraphicsView; + std::shared_ptr mCvImageData; + +public: + void setInData(std::shared_ptr, int) override; + std::shared_ptr outData(PortIndex port) override; + +}; \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvGraphicsViewWidget.cpp b/NodeEditorPro/examples/opcv/CvGraphicsViewWidget.cpp new file mode 100644 index 0000000..750b578 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvGraphicsViewWidget.cpp @@ -0,0 +1,94 @@ +#include "opcv/CvGraphicsViewWidget.h" + +#include + +CvGraphicsViewWidget::CvGraphicsViewWidget(QWidget* parent) + :QGraphicsView(parent) +{ + if (parent != Q_NULLPTR) + this->setParent(parent); + this->setStyleSheet("QGraphicsView{" + "background-color:transparent;" + "border:none;" + "}"); + mGraphicsScene = new QGraphicsScene(); + + setMouseTracking(true);//λ + setDragMode(QGraphicsView::NoDrag); + setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + setResizeAnchor(QGraphicsView::AnchorUnderMouse); + +} + +void CvGraphicsViewWidget::showImage(cv::Mat const& _cvimg) +{ + QThread* thread =new QThread(); + qDebug() << "CvGraphicsViewWidget::showImage thread:"<currentThreadId(); + + if (_cvimg.empty()) + return; + //QImage toShow; + QPixmap pixmap; + + CvImage2QPixmap(_cvimg, pixmap); + + //QPixmap pixmap = QPixmap::fromImage(toShow); + mGraphicsScene->clear(); + mGraphicsScene->addPixmap(pixmap); + + setScene(mGraphicsScene); +} + +//void CvGraphicsViewWidget::paintEvent(QPaintEvent* event) +//{ +// QGraphicsView::paintEvent(event); +//} + +void CvGraphicsViewWidget::CvImage2QPixmap(cv::Mat const& fromCv, QPixmap& toPix) +{ + const uchar* pSrc = (const uchar*)fromCv.data; + QImage image; + if (fromCv.type() == CV_8UC1) + { + qDebug() << "CV_8UC1"; + image = QImage(pSrc, fromCv.cols, fromCv.rows, fromCv.step, QImage::Format_Grayscale8); + } + else if (fromCv.type() == CV_8UC3) + { + qDebug() << "CV_8UC3"; + image = QImage(pSrc, fromCv.cols, fromCv.rows, fromCv.step, QImage::Format_RGB888); + } + else if (fromCv.type() == CV_8UC4) + { + qDebug() << "CV_8UC4"; + image = QImage(pSrc, fromCv.cols, fromCv.rows, fromCv.step, QImage::Format_ARGB32); + } + else + { + qDebug() << "ERROR: Mat could not be converted to QImage."; + return; + } + toPix = QPixmap::fromImage(image); +} + +bool CvGraphicsViewWidget::QImage2CvImage(QImage& fromQ, cv::Mat& toCv) +{ + + return false; +} + +//void CvGraphicsViewWidget::wheelEvent(QWheelEvent* ev) +//{ +// qreal qrTmp = 1.0; +// if (ev->delta() > 0) +// { +// qrTmp = 1.2; +// this->scale(qrTmp, qrTmp); +// } +// else +// { +// qrTmp = 1.0 / 1.2; +// this->scale(qrTmp, qrTmp); +// } +// mScaledFactor *= qrTmp; //Ŵ +//} \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvGraphicsViewWidget.h b/NodeEditorPro/examples/opcv/CvGraphicsViewWidget.h new file mode 100644 index 0000000..cfdf378 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvGraphicsViewWidget.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +//#include +//#include + +#include + +class CvGraphicsViewWidget :public QGraphicsView +{ + Q_OBJECT + +public: + CvGraphicsViewWidget(QWidget* parent = Q_NULLPTR); + virtual ~CvGraphicsViewWidget() {} + +public: + void showImage(cv::Mat const& _himg); + + static void CvImage2QPixmap(cv::Mat const& fromCv, QPixmap& toPix); + static bool QImage2CvImage(QImage& fromQ, cv::Mat& toCv); + //static void QPixmapToCvRegion(QPixmap const& _pix, cv::Rect2d& tarImg); + +protected: + //void paintEvent(QPaintEvent* event) override; + //void wheelEvent(QWheelEvent* ev); + +private: + QGraphicsScene* mGraphicsScene; + //QGraphicsRectItem* item; + + //QColor penColor = QColor(0, 180, 255);//ɫ + //int penWidth = 2;//ʿ + qreal mScaledFactor; //ű + +}; \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvImageData.h b/NodeEditorPro/examples/opcv/CvImageData.h new file mode 100644 index 0000000..93dfcb8 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageData.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" +#include + +using QtNodes::NodeData; +using QtNodes::NodeDataType; + +class CvImageData :public NodeData +{ +public: + CvImageData() { mCvImage = cv::Mat(); } + CvImageData(cv::Mat inImg) + { + if (!inImg.empty()) + mCvImage = inImg; + else + mCvImage = cv::Mat(); + } + virtual ~CvImageData() {} + + NodeDataType type() const override { return { "CvImage","CvImg" }; } + + bool empty() { return mCvImage.empty(); } + + cv::Mat* CvImage() { return &mCvImage; } + + void setCvImage(cv::Mat const& _img) + { + if (!_img.empty()) + mCvImage = _img; + else + return; + } +private: + cv::Mat mCvImage; + +}; \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvImageLoaderModel.cpp b/NodeEditorPro/examples/opcv/CvImageLoaderModel.cpp new file mode 100644 index 0000000..2bf37fa --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageLoaderModel.cpp @@ -0,0 +1,94 @@ +#include "opcv/CvImageLoaderModel.h" + +#include +#include +#include + +CvImageLoaderModel::CvImageLoaderModel() +{ + mCvImageView = new CvImageViewWidget(); + mCvImageView->installEventFilter(this); + mCvImageView->resize(200, 200); + mCvImageData = std::make_shared(); +} + +unsigned int CvImageLoaderModel::nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 0; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeDataType CvImageLoaderModel::dataType(PortType portType, PortIndex portIndex) const +{ + return CvImageData().type(); +} + +QJsonObject CvImageLoaderModel::save() const +{ + + return QJsonObject(); +} + +void CvImageLoaderModel::restore(QJsonObject const&) +{ + QJsonObject modelJson = NodeDataModel::save(); + +} + +void CvImageLoaderModel::loadImage(QString fileName) +{ + if (fileName == "") + return; + + cv::Mat tempImg = cv::imread(fileName.toStdString().c_str()); + mCvImageData->setCvImage(tempImg); + mCvImageView->showImage(*mCvImageData->CvImage()); +} + +bool CvImageLoaderModel::eventFilter(QObject* object, QEvent* event) +{ + if (object == mCvImageView) + { + if (event->type() == QEvent::MouseButtonPress) + { + imageName = QFileDialog::getOpenFileName(nullptr, + tr("Open Image"), + QDir::homePath(), + tr("Image Files(*.png *.jpg *.jpeg *.bmp)")); + if (imageName == "") + { + return false; + } + + loadImage(imageName); + + Q_EMIT dataUpdated(0); + + return true; + } + else if (event->type() == QEvent::Resize) + { + } + } + return false; +} + +std::shared_ptr CvImageLoaderModel::outData(PortIndex port) +{ + return std::dynamic_pointer_cast(mCvImageData); +} + diff --git a/NodeEditorPro/examples/opcv/CvImageLoaderModel.h b/NodeEditorPro/examples/opcv/CvImageLoaderModel.h new file mode 100644 index 0000000..75c040f --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageLoaderModel.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include +#include +#include + +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" + +#include "opcv/CvImageViewWidget.h" +#include "opcv/CvImageData.h" + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +//class CvImageData; +//class CvImageViewWidget; + +class CvImageLoaderModel :public NodeDataModel +{ + Q_OBJECT + +public: + CvImageLoaderModel(); + virtual ~CvImageLoaderModel() {} + +public: + QString caption() const override { return QStringLiteral("cv图像输入"); } + QString name() const override { return QStringLiteral("cv图像输入"); } + virtual QString modelName() const { return QStringLiteral("cv图像输入"); } + + void setInData(std::shared_ptr, int) override { } + QWidget* embeddedWidget() override { return mCvImageView; } + bool resizable() const override { return false; } + + unsigned int nPorts(PortType portType) const override; + NodeDataType dataType(PortType portType, PortIndex portIndex) const override; + std::shared_ptr outData(PortIndex port) override; + + QJsonObject save() const override; + void restore(QJsonObject const&) override; + void loadImage(QString fileName); + +protected: + bool eventFilter(QObject* object, QEvent* event) override; + +private: + QString imageName; + CvImageViewWidget* mCvImageView; + + std::shared_ptr mCvImageData; + +}; \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvImageRGB2GrayModel.cpp b/NodeEditorPro/examples/opcv/CvImageRGB2GrayModel.cpp new file mode 100644 index 0000000..74f1df1 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageRGB2GrayModel.cpp @@ -0,0 +1,104 @@ +#include "opcv/CvImageRGB2GrayModel.h" + +#include +#include + +CvImageRGB2GrayModel::CvImageRGB2GrayModel() +{ + qRegisterMetaType("cv::Mat"); + mCvImage = std::make_shared(); +} + +unsigned int CvImageRGB2GrayModel::nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + case PortType::Out: + result = 1; + default: + break; + } + + return result; +} + +bool CvImageRGB2GrayModel::RunTask() +{ + Q_EMIT computingStarted(); + PortIndex const outPortIndex = 0; + + try + { + qDebug() << "CvImageRGB2GrayModel::RunTask thread:" << QThread::currentThreadId(); + + mAlgoTool = new CvAlgorithmTools(); + mChildThread = new QThread(); + mAlgoTool->moveToThread(mChildThread); + + QObject::connect(mChildThread, &QThread::finished, mChildThread, &QObject::deleteLater); + QObject::connect(mChildThread, &QThread::finished, mAlgoTool, &QObject::deleteLater); + + QObject::connect(this, &CvImageRGB2GrayModel::SignalCvImageRgb2Gray, mAlgoTool, &CvAlgorithmTools::CvImageRgb2Gray); + QObject::connect(mAlgoTool, &CvAlgorithmTools::sendCvImageRgb2GrayResult, this, &CvImageRGB2GrayModel::GetRgb2GrayResult); + + mChildThread->start(); + + cv::Mat src; + mCvImage->CvImage()->copyTo(src); + if (!src.empty()) + { + emit computingStarted(); + emit SignalCvImageRgb2Gray(src); + + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("ȱʧʧ!"); + } + return true; +} + +void CvImageRGB2GrayModel::GetRgb2GrayResult(cv::Mat grayImg) +{ + mCvImage->setCvImage(grayImg); + + if (mAlgoTool) + { + mChildThread->quit(); + mChildThread->wait(); + mAlgoTool = Q_NULLPTR; + mChildThread = Q_NULLPTR; + } + + PortIndex const outPortIndex = 0; + Q_EMIT dataUpdated(outPortIndex); + Q_EMIT computingFinished(); +} + +void CvImageRGB2GrayModel::setInData(std::shared_ptrdata , int) +{ + if (data == nullptr) + return; + if (data->type() == mCvImage->type()) + { + auto dataPtr = std::dynamic_pointer_cast(data); + if (dataPtr->CvImage()->empty()) + return; + mCvImage->setCvImage(*dataPtr->CvImage()); + } + RunTask(); +} + +std::shared_ptr CvImageRGB2GrayModel::outData(PortIndex port) +{ + return std::dynamic_pointer_cast(mCvImage); +} \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvImageRGB2GrayModel.h b/NodeEditorPro/examples/opcv/CvImageRGB2GrayModel.h new file mode 100644 index 0000000..269043d --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageRGB2GrayModel.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include + +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" + +#include "opcv/CvImageData.h" +#include "opcv/CvAlgorithmTools.h" + +#include +#include + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +class CvImageRGB2GrayModel :public NodeDataModel +{ + Q_OBJECT +public: + CvImageRGB2GrayModel(); + virtual ~CvImageRGB2GrayModel() {} + +public: + QString caption() const override { return QStringLiteral("cvͼתҶ"); } + QString name() const override { return QStringLiteral("cvͼתҶ"); } + virtual QString modelName() const { return QStringLiteral("cvͼתҶ"); } + + QWidget* embeddedWidget() override { return Q_NULLPTR; } + + bool resizable() const override { return false; } + + NodeValidationState validationState() const override { return modelValidationState; } + QString validationMessage() const override { return modelValidationError; } + + unsigned int nPorts(PortType portType) const override; + + NodeDataType dataType(PortType portType, PortIndex portIndex) const override { return CvImageData().type(); }; + + void setInData(std::shared_ptr, int) override; + std::shared_ptr outData(PortIndex port) override; + +protected: + bool RunTask(); + +signals: + void SignalCvImageRgb2Gray(cv::Mat rgbImg); + +public slots: + void GetRgb2GrayResult(cv::Mat grayImg); + +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("ͼƬδ!"); + +private: + CvAlgorithmTools* mAlgoTool = Q_NULLPTR; + QThread* mChildThread = Q_NULLPTR; + + std::shared_ptr mCvImage; +}; + diff --git a/NodeEditorPro/examples/opcv/CvImageShowModel.cpp b/NodeEditorPro/examples/opcv/CvImageShowModel.cpp new file mode 100644 index 0000000..22395b5 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageShowModel.cpp @@ -0,0 +1,128 @@ +#include "opcv/CvImageShowModel.h" + +CvImageShowModel::CvImageShowModel() +{ + //moveToThread(this); + + mCvImageView = new CvImageViewWidget(); + mCvImageView->installEventFilter(this); + mCvImageView->resize(200, 200); + mCvImageData = std::make_shared(); + //m_hRegion = std::make_shared(); +} + +void CvImageShowModel::inputConnectionDeleted(QtNodes::Connection const&) +{ + mCvImageView->showImage(cv::Mat()); + + mCvImageData = std::make_shared(); + + PortIndex const outPortIndex = 0; + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("ͼƬδ!"); + Q_EMIT dataUpdated(outPortIndex); +} + +bool CvImageShowModel::RunTask() +{ + PortIndex const outPortIndex = 0; + try + { + //qDebug() << "22show"; + //QThread::sleep(3); + + mCvImageView->showImage(*mCvImageData->CvImage()); + modelValidationState = NodeValidationState::Valid; + modelValidationError = QString(); + } + catch (...) + { + modelValidationState = NodeValidationState::Warning; + modelValidationError = QStringLiteral("ȱʧʧ!"); + } + + Q_EMIT dataUpdated(outPortIndex); + + return true; +} + +bool CvImageShowModel::eventFilter(QObject* watched, QEvent* event) +{ + + return false; +} + +unsigned int CvImageShowModel::nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) + { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +NodeDataType CvImageShowModel::dataType(PortType portType, PortIndex portIndex) const +{ + switch (portIndex) + { + case 0: + return CvImageData().type(); + break; + case 1: + return CvImageData().type(); + break; + } + return CvImageData().type(); +} + +void CvImageShowModel::setInData(std::shared_ptr data, int portIndex) +{ + if (data == nullptr) + return; + if (data->type() == mCvImageData->type()) + { + auto dataPtr = std::dynamic_pointer_cast(data); + if (dataPtr->CvImage()->empty()) + return; + mCvImageData->setCvImage(*dataPtr->CvImage()); + } + //else if (data->type() == m_hRegion->type()) + //{ + // auto dataPtr = std::dynamic_pointer_cast(data); + // if (!dataPtr->hRegion()->IsInitialized()) + // return; + // m_hRegion->setHRegion(*dataPtr->hRegion()); + // m_hRegion->setSize(dataPtr->getSize()); + // HImage tmpImg = m_hRegion->hRegion()->RegionToBin(255, 0, + // m_hRegion->getSize().width(), m_hRegion->getSize().height()); + // m_hImage->setHImage(tmpImg); + //} + RunTask(); +} + + + +std::shared_ptr CvImageShowModel::outData(PortIndex index) +{ + switch (index) + { + case 0: + return std::dynamic_pointer_cast(mCvImageData); + break; + case 1: + return std::dynamic_pointer_cast(mCvImageData); + break; + } + return std::dynamic_pointer_cast(mCvImageData); +} \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvImageShowModel.h b/NodeEditorPro/examples/opcv/CvImageShowModel.h new file mode 100644 index 0000000..7db1584 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageShowModel.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "DataModelRegistry.hpp" +#include "NodeDataModel.hpp" + +#include "opcv/CvImageViewWidget.h" +#include "opcv/CvImageData.h" + +#include + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeValidationState; + +class CvImageShowModel :public NodeDataModel +{ + Q_OBJECT + +public: + CvImageShowModel(); + virtual ~CvImageShowModel() {} + +public: + QString caption() const override { return QStringLiteral("cvͼʾ"); } + QString name() const override { return QStringLiteral("cvͼʾ"); } + virtual QString modelName() const { return QStringLiteral("cvͼʾ"); } + + QWidget* embeddedWidget() override { return mCvImageView; } + bool resizable() const override { return false; } + + unsigned int nPorts(PortType portType) const override; + NodeDataType dataType(PortType portType, PortIndex portIndex) const override; + + NodeValidationState validationState() const override { return modelValidationState; } + QString validationMessage() const override { return modelValidationError; } + +public Q_SLOTS: + void inputConnectionDeleted(QtNodes::Connection const&) override; + +protected: + bool RunTask(); + bool eventFilter(QObject* watched, QEvent* event) override; + +public: + NodeValidationState modelValidationState = NodeValidationState::Warning; + QString modelValidationError = QStringLiteral("ͼƬδ!"); + +private: + CvImageViewWidget* mCvImageView; + + std::shared_ptr mCvImageData; + //std::shared_ptr mCvRect; + +public: + std::shared_ptr outData(PortIndex port) override; + void setInData(std::shared_ptr, int) override; + +}; \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/CvImageViewWidget.cpp b/NodeEditorPro/examples/opcv/CvImageViewWidget.cpp new file mode 100644 index 0000000..746af4b --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageViewWidget.cpp @@ -0,0 +1,85 @@ +#include "opcv/CvImageViewWidget.h" + +#include + +CvImageViewWidget::CvImageViewWidget(QWidget* parent) +{ + if (parent != Q_NULLPTR) + this->setParent(parent); + this->setStyleSheet("background-color:black;"); + curPixmap = new QPixmap(); +} + +void CvImageViewWidget::showImage(cv::Mat const& _cvimg) +{ + if (_cvimg.empty()) + { + curPixmap = new QPixmap(); + this->setPixmap(*curPixmap); + this->update(); + return; + } + + int width; + int height; + double zoomRatio = 1.0; + + width = _cvimg.cols; + height = _cvimg.rows; + + if (width > this->width()) + zoomRatio = 1.0 * this->width() / width; + + cv::resize(_cvimg, curImage, cv::Size(width * zoomRatio, height * zoomRatio)); + CvImageToQPixmap(curImage, *curPixmap); + + this->update(); +} + +void CvImageViewWidget::CvImageToQPixmap(cv::Mat const& _img, QPixmap& tarImg) +{ + const uchar* pSrc = (const uchar*)_img.data; + QImage image; + if (_img.type() == CV_8UC1) + { + qDebug() << "CV_8UC1"; + image = QImage(pSrc, _img.cols, _img.rows, _img.step, QImage::Format_Grayscale8); + } + else if (_img.type() == CV_8UC3) + { + qDebug() << "CV_8UC3"; + image = QImage(pSrc, _img.cols, _img.rows, _img.step, QImage::Format_RGB888); + } + else if (_img.type() == CV_8UC4) + { + qDebug() << "CV_8UC4"; + image = QImage(pSrc, _img.cols, _img.rows, _img.step, QImage::Format_ARGB32); + } + else + { + qDebug() << "ERROR: Mat could not be converted to QImage."; + return; + } + tarImg = QPixmap::fromImage(image); +} + +bool CvImageViewWidget::QImage2CvImage(QImage& from, cv::Mat& to) +{ + + return false; +} + +void CvImageViewWidget::QPixmapToCvRegion(QPixmap const& _pix, cv::Rect2d& tarImg) +{ + +} + +void CvImageViewWidget::paintEvent(QPaintEvent* event) +{ + //QPainter painter(this); + if (!curPixmap->isNull()) + { + this->setPixmap(curPixmap->scaled(this->width(), this->height(), Qt::KeepAspectRatio)); + } + QLabel::paintEvent(event); +} diff --git a/NodeEditorPro/examples/opcv/CvImageViewWidget.h b/NodeEditorPro/examples/opcv/CvImageViewWidget.h new file mode 100644 index 0000000..81e8823 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvImageViewWidget.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +class CvImageViewWidget :public QLabel +{ + Q_OBJECT + +public: + CvImageViewWidget(QWidget* parent = Q_NULLPTR); + virtual ~CvImageViewWidget() {} + +public: + void showImage(cv::Mat const& _himg); + + static void CvImageToQPixmap(cv::Mat const& _img, QPixmap& tarImg); + static bool QImage2CvImage(QImage& from, cv::Mat& to); + static void QPixmapToCvRegion(QPixmap const& _pix, cv::Rect2d& tarImg); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + cv::Mat curImage; + QPixmap* curPixmap; + // 实例化画家对象,this指定的是绘图设备 + QPainter painter; +}; diff --git a/NodeEditorPro/examples/opcv/CvRectData.h b/NodeEditorPro/examples/opcv/CvRectData.h new file mode 100644 index 0000000..ba78f15 --- /dev/null +++ b/NodeEditorPro/examples/opcv/CvRectData.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "NodeDataModel.hpp" + +#include + +using QtNodes::NodeData; +using QtNodes::NodeDataType; + +class CvRectData :public NodeData +{ +public: + CvRectData() { mCvRect = cv::Rect(); } + CvRectData(cv::Rect& _rect) + { + if (!_rect.empty()) + mCvRect = _rect; + } + + NodeDataType type() const override { return { "CvRect","Rect" }; } + + cv::Rect* CvRect() { return &mCvRect; } + void setCvRect(cv::Rect const& _rect) + { + if (_rect.empty()) + return; + mCvRect = _rect; + } + void setSize(QSize const& _size) { mSize = _size; } + QSize getSize() { return mSize; } + +private: + cv::Rect mCvRect; + QSize mSize; +}; \ No newline at end of file diff --git a/NodeEditorPro/examples/opcv/MoudleOpencvNodes.h b/NodeEditorPro/examples/opcv/MoudleOpencvNodes.h new file mode 100644 index 0000000..88e9bc9 --- /dev/null +++ b/NodeEditorPro/examples/opcv/MoudleOpencvNodes.h @@ -0,0 +1,7 @@ +#pragma once + +#include "opcv/CvImageLoaderModel.h" +#include "opcv/CvImageShowModel.h" +#include "opcv/CvImageRGB2GrayModel.h" + +#include "opcv/CvGraphicsShowModel.h" diff --git a/NodeEditorPro/examples/opcv/Widget.ui b/NodeEditorPro/examples/opcv/Widget.ui new file mode 100644 index 0000000..6a3507e --- /dev/null +++ b/NodeEditorPro/examples/opcv/Widget.ui @@ -0,0 +1,83 @@ + + + Form + + + + 0 + 0 + 391 + 293 + + + + Form + + + + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + 图片缩放 + + + + + + + -20 + + + 20 + + + 2 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + diff --git a/NodeEditorPro/halcon20.11_x64_cpp.props b/NodeEditorPro/halcon20.11_x64_cpp.props new file mode 100644 index 0000000..fb139df --- /dev/null +++ b/NodeEditorPro/halcon20.11_x64_cpp.props @@ -0,0 +1,19 @@ + + + + + + + <_ProjectFileVersion>14.0.25431.1 + + + + B:\CodeDeps\halconlibs\include;%(AdditionalIncludeDirectories) + + + halcon.lib;halconcpp.lib;%(AdditionalDependencies) + B:\CodeDeps\halconlibs\libs;%(AdditionalLibraryDirectories) + + + + \ No newline at end of file diff --git a/NodeEditorPro/include/nodes/Compiler.hpp b/NodeEditorPro/include/nodes/Compiler.hpp new file mode 100644 index 0000000..7f36c22 --- /dev/null +++ b/NodeEditorPro/include/nodes/Compiler.hpp @@ -0,0 +1,53 @@ +#pragma once + +#if \ + defined (__MINGW32__) || \ + defined (__MINGW64__) +# define NODE_EDITOR_COMPILER "MinGW" +# define NODE_EDITOR_COMPILER_MINGW +#elif \ + defined (__GNUC__) +# define NODE_EDITOR_COMPILER "GNU" +# define NODE_EDITOR_COMPILER_GNU +# define NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR __GNUC__ +# define NODE_EDITOR_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__ +# define NODE_EDITOR_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__ +#elif \ + defined (__clang__) +# define NODE_EDITOR_COMPILER "Clang" +# define NODE_EDITOR_COMPILER_CLANG +#elif \ + defined (_MSC_VER) +# define NODE_EDITOR_COMPILER "Microsoft Visual C++" +# define NODE_EDITOR_COMPILER_MICROSOFT +#elif \ + defined (__BORLANDC__) +# define NODE_EDITOR_COMPILER "Borland C++ Builder" +# define NODE_EDITOR_COMPILER_BORLAND +#elif \ + defined (__CODEGEARC__) +# define NODE_EDITOR_COMPILER "CodeGear C++ Builder" +# define NODE_EDITOR_COMPILER_CODEGEAR +#elif \ + defined (__INTEL_COMPILER) || \ + defined (__ICL) +# define NODE_EDITOR_COMPILER "Intel C++" +# define NODE_EDITOR_COMPILER_INTEL +#elif \ + defined (__xlC__) || \ + defined (__IBMCPP__) +# define NODE_EDITOR_COMPILER "IBM XL C++" +# define NODE_EDITOR_COMPILER_IBM +#elif \ + defined (__HP_aCC) +# define NODE_EDITOR_COMPILER "HP aC++" +# define NODE_EDITOR_COMPILER_HP +#elif \ + defined (__WATCOMC__) +# define NODE_EDITOR_COMPILER "Watcom C++" +# define NODE_EDITOR_COMPILER_WATCOM +#endif + +#ifndef NODE_EDITOR_COMPILER +# error "Current compiler is not supported." +#endif diff --git a/NodeEditorPro/include/nodes/Connection.hpp b/NodeEditorPro/include/nodes/Connection.hpp new file mode 100644 index 0000000..1d2307e --- /dev/null +++ b/NodeEditorPro/include/nodes/Connection.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include + +#include "PortType.hpp" +#include "NodeData.hpp" + +#include "Serializable.hpp" +#include "ConnectionState.hpp" +#include "ConnectionGeometry.hpp" +#include "TypeConverter.hpp" +#include "QUuidStdHash.hpp" +#include "Export.hpp" +#include "memory.hpp" + +class QPointF; + +namespace QtNodes +{ + + class Node; + class NodeData; + class ConnectionGraphicsObject; + + /** + * \brief 节点连接线 + */ + class Connection + : public QObject + , public Serializable + { + + Q_OBJECT + + public: + + /// 新的连接对象会连接到目标节点,连接的端口应当有 端口类型,端口序号 + /// 连接对象的另一端应当是相反的端口。 + Connection(PortType portType, + Node& node, + PortIndex portIndex); + + Connection(Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter converter = + TypeConverter{}); + + Connection(const Connection&) = delete; + Connection operator=(const Connection&) = delete; + + ~Connection(); + + public: + + QJsonObject + save() const override; + + public: + + QUuid + id() const; + + /// Remembers the end being dragged. + /// Invalidates Node address. + /// Grabs mouse. + void + setRequiredPort(PortType portType); + PortType + requiredPort() const; + + void + setGraphicsObject(std::unique_ptr&& graphics); + + /// Assigns a node to the required port. + /// It is assumed that there is a required port, no extra checks + void + setNodeToPort(Node& node, + PortType portType, + PortIndex portIndex); + + void + removeFromNodes() const; + + public: + + ConnectionGraphicsObject& + getConnectionGraphicsObject() const; + + ConnectionState const& + connectionState() const; + ConnectionState& + connectionState(); + + ConnectionGeometry& + connectionGeometry(); + + ConnectionGeometry const& + connectionGeometry() const; + + Node* + getNode(PortType portType) const; + + Node*& + getNode(PortType portType); + + PortIndex + getPortIndex(PortType portType) const; + + void + clearNode(PortType portType); + + NodeDataType + dataType(PortType portType) const; + + void + setTypeConverter(TypeConverter converter); + + bool + complete() const; + + public: // data propagation + + void + propagateData(std::shared_ptr nodeData) const; + + void + propagateEmptyData() const; + + Q_SIGNALS: + + void + connectionCompleted(Connection const&) const; + + void + connectionMadeIncomplete(Connection const&) const; + + private: + + QUuid _uid; + + private: + + Node* _outNode = nullptr; + Node* _inNode = nullptr; + + PortIndex _outPortIndex; + PortIndex _inPortIndex; + + private: + + ConnectionState _connectionState; + ConnectionGeometry _connectionGeometry; + + std::unique_ptr_connectionGraphicsObject; + + TypeConverter _converter; + + Q_SIGNALS: + + void + updated(Connection& conn) const; + }; +} diff --git a/NodeEditorPro/include/nodes/ConnectionGeometry.hpp b/NodeEditorPro/include/nodes/ConnectionGeometry.hpp new file mode 100644 index 0000000..bd3a844 --- /dev/null +++ b/NodeEditorPro/include/nodes/ConnectionGeometry.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "PortType.hpp" + +#include +#include + +#include + +namespace QtNodes +{ + + class ConnectionGeometry + { + public: + + ConnectionGeometry(); + + public: + + QPointF const& + getEndPoint(PortType portType) const; + + void + setEndPoint(PortType portType, QPointF const& point); + + void + moveEndPoint(PortType portType, QPointF const& offset); + + QRectF + boundingRect() const; + + std::pair + pointsC1C2() const; + + QPointF + source() const { return _out; } + QPointF + sink() const { return _in; } + + double + lineWidth() const { return _lineWidth; } + + bool + hovered() const { return _hovered; } + void + setHovered(bool hovered) { _hovered = hovered; } + + private: + // local object coordinates + QPointF _in; + QPointF _out; + + //int _animationPhase; + + double _lineWidth; + + bool _hovered; + }; +} diff --git a/NodeEditorPro/include/nodes/ConnectionGraphicsObject.hpp b/NodeEditorPro/include/nodes/ConnectionGraphicsObject.hpp new file mode 100644 index 0000000..85a20ba --- /dev/null +++ b/NodeEditorPro/include/nodes/ConnectionGraphicsObject.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include + +class QGraphicsSceneMouseEvent; + +namespace QtNodes +{ + + class FlowScene; + class Connection; + class ConnectionGeometry; + class Node; + + /// Graphic Object for connection. Adds itself to scene + class ConnectionGraphicsObject + : public QGraphicsObject + { + Q_OBJECT + + public: + + ConnectionGraphicsObject(FlowScene& scene, + Connection& connection); + + virtual + ~ConnectionGraphicsObject(); + + enum { Type = UserType + 2 }; + int + type() const override { return Type; } + + public: + + Connection& + connection(); + + QRectF + boundingRect() const override; + + QPainterPath + shape() const override; + + void + setGeometryChanged(); + + /// Updates the position of both ends + void + move(); + + void + lock(bool locked); + + protected: + + void + paint(QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget* widget = 0) override; + + void + mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + void + mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; + + void + mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; + + void + hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; + + void + hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; + + private: + + void + addGraphicsEffect(); + + private: + + FlowScene& _scene; + + Connection& _connection; + }; +} diff --git a/NodeEditorPro/include/nodes/ConnectionState.hpp b/NodeEditorPro/include/nodes/ConnectionState.hpp new file mode 100644 index 0000000..1b764c6 --- /dev/null +++ b/NodeEditorPro/include/nodes/ConnectionState.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include "PortType.hpp" + +class QPointF; + +namespace QtNodes +{ + + class Node; + + /// Stores currently draggind end. + /// Remembers last hovered Node. + class ConnectionState + { + public: + + ConnectionState(PortType port = PortType::None) + : _requiredPort(port) + {} + + ConnectionState(const ConnectionState&) = delete; + ConnectionState operator=(const ConnectionState&) = delete; + + ~ConnectionState(); + + public: + + void setRequiredPort(PortType end) + { + _requiredPort = end; + } + + PortType requiredPort() const + { + return _requiredPort; + } + + bool requiresPort() const + { + return _requiredPort != PortType::None; + } + + void setNoRequiredPort() + { + _requiredPort = PortType::None; + } + + public: + + void interactWithNode(Node* node); + + void setLastHoveredNode(Node* node); + + Node* + lastHoveredNode() const + { + return _lastHoveredNode; + } + + void resetLastHoveredNode(); + + private: + + PortType _requiredPort; + + Node* _lastHoveredNode{ nullptr }; + }; +} diff --git a/NodeEditorPro/include/nodes/ConnectionStyle.hpp b/NodeEditorPro/include/nodes/ConnectionStyle.hpp new file mode 100644 index 0000000..53a79c7 --- /dev/null +++ b/NodeEditorPro/include/nodes/ConnectionStyle.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "Style.hpp" + +namespace QtNodes +{ + + class ConnectionStyle : public Style + { + public: + + ConnectionStyle(); + + ConnectionStyle(QString jsonText); + + public: + + static void setConnectionStyle(QString fileName); + + private: + + void loadJsonFile(QString fileName) override; + + void saveJsonFile(QString fileName) override; + + public: + + QColor constructionColor() const; + QColor normalColor() const; + QColor normalColor(QString typeId) const; + QColor selectedColor() const; + QColor selectedHaloColor() const; + QColor hoveredColor() const; + + float lineWidth() const; + float constructionLineWidth() const; + float pointDiameter() const; + + bool useDataDefinedColors() const; + + public: + + QColor ConstructionColor; + QColor NormalColor; + QColor SelectedColor; + QColor SelectedHaloColor; + QColor HoveredColor; + + float LineWidth; + float ConstructionLineWidth; + float PointDiameter; + + bool UseDataDefinedColors; + }; +} diff --git a/NodeEditorPro/include/nodes/DataModelRegistry.hpp b/NodeEditorPro/include/nodes/DataModelRegistry.hpp new file mode 100644 index 0000000..2ccc138 --- /dev/null +++ b/NodeEditorPro/include/nodes/DataModelRegistry.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "NodeDataModel.hpp" +#include "TypeConverter.hpp" +#include "Export.hpp" +#include "QStringStdHash.hpp" +#include "memory.hpp" + +namespace QtNodes +{ + + /// Class uses map for storing models (name, model) + class DataModelRegistry + { + + public: + + using RegistryItemPtr = std::unique_ptr; + using RegistryItemCreator = std::function; + using RegisteredModelCreatorsMap = std::unordered_map; + using RegisteredModelsCategoryMap = std::unordered_map; + using CategoriesSet = std::set; + + using RegisteredTypeConvertersMap = std::unordered_map< + TypeConverterId, TypeConverter, TypeConverterIdHash>; + + DataModelRegistry() = default; + ~DataModelRegistry() = default; + + DataModelRegistry(DataModelRegistry const&) = delete; + DataModelRegistry(DataModelRegistry&&) = default; + + DataModelRegistry& operator=(DataModelRegistry const&) = delete; + DataModelRegistry& operator=(DataModelRegistry&&) = default; + + public: + + template + void registerModel(RegistryItemCreator creator, + QString const& category = u8"默认节点") + { + const QString name = computeName(HasStaticMethodName{}, creator); + if (!_registeredItemCreators.count(name)) + { + _registeredItemCreators[name] = std::move(creator); + _categories.insert(category); + _registeredModelsCategory[name] = category; + } + } + + template + void registerModel(QString const& category = u8"默认节点") + { + RegistryItemCreator creator = []() { return std::make_unique(); }; + registerModel(std::move(creator), category); + } + + template + void registerModel(QString const& category, + RegistryItemCreator creator) + { + registerModel(std::move(creator), category); + } + + template + void registerModel(ModelCreator&& creator, QString const& category = u8"默认节点") + { + using ModelType = compute_model_type_t; + registerModel(std::forward(creator), category); + } + + template + void registerModel(QString const& category, ModelCreator&& creator) + { + registerModel(std::forward(creator), category); + } + + void registerTypeConverter(TypeConverterId const& id, + TypeConverter typeConverter) + { + _registeredTypeConverters[id] = std::move(typeConverter); + } + + std::unique_ptrcreate(QString const& modelName); + + RegisteredModelCreatorsMap const& registeredModelCreators() const; + + RegisteredModelsCategoryMap const& registeredModelsCategoryAssociation() const; + + CategoriesSet const& categories() const; + + TypeConverter getTypeConverter(NodeDataType const& d1, + NodeDataType const& d2) const; + + private: + + RegisteredModelsCategoryMap _registeredModelsCategory; + + CategoriesSet _categories; + + RegisteredModelCreatorsMap _registeredItemCreators; + + RegisteredTypeConvertersMap _registeredTypeConverters; + + private: + + template + struct HasStaticMethodName + : std::false_type + {}; + + template + struct HasStaticMethodName::value>::type> + : std::true_type + {}; + + template + static QString + computeName(std::true_type, RegistryItemCreator const&) + { + return ModelType::Name(); + } + + template + static QString + computeName(std::false_type, RegistryItemCreator const& creator) + { + return creator()->name(); + } + + template + struct UnwrapUniquePtr + { + // Assert always fires, but the compiler doesn't know this: + static_assert(!std::is_same::value, + "The ModelCreator must return a std::unique_ptr, where T " + "inherits from NodeDataModel"); + }; + + template + struct UnwrapUniquePtr> + { + static_assert(std::is_base_of::value, + "The ModelCreator must return a std::unique_ptr, where T " + "inherits from NodeDataModel"); + using type = T; + }; + + template + using compute_model_type_t = typename UnwrapUniquePtr::type; + }; + + + +} diff --git a/NodeEditorPro/include/nodes/Export.hpp b/NodeEditorPro/include/nodes/Export.hpp new file mode 100644 index 0000000..2a59bf6 --- /dev/null +++ b/NodeEditorPro/include/nodes/Export.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "Compiler.hpp" +#include "OperatingSystem.hpp" + +#ifdef NODE_EDITOR_PLATFORM_WINDOWS +# define NODE_EDITOR_EXPORT __declspec(dllexport) +# define NODE_EDITOR_IMPORT __declspec(dllimport) +# define NODE_EDITOR_LOCAL +#elif \ + NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >= 4 || \ + defined (NODE_EDITOR_COMPILER_CLANG) +# define NODE_EDITOR_EXPORT __attribute__((visibility("default"))) +# define NODE_EDITOR_IMPORT __attribute__((visibility("default"))) +# define NODE_EDITOR_LOCAL __attribute__((visibility("hidden"))) +#else +# define NODE_EDITOR_EXPORT +# define NODE_EDITOR_IMPORT +# define NODE_EDITOR_LOCAL +#endif + +#ifdef __cplusplus +# define NODE_EDITOR_DEMANGLED extern "C" +#else +# define NODE_EDITOR_DEMANGLED +#endif + + +//#if defined (NODE_EDITOR_SHARED) && !defined (NODE_EDITOR_STATIC) +//# ifdef NODE_EDITOR_EXPORTS +//# define NODE_EDITOR_EXPORT +//# else +//# define NODE_EDITOR_IMPORT +//# endif +//# define NODE_EDITOR_PRIVATE NODE_EDITOR_LOCAL +//#elif !defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) +//# define +//# define NODE_EDITOR_PRIVATE +//#elif defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) +//# ifdef NODE_EDITOR_EXPORTS +//# error "Cannot build as shared and static simultaneously." +//# else +//# error "Cannot link against shared and static simultaneously." +//# endif +//#else +//# ifdef NODE_EDITOR_EXPORTS +//# error "Choose whether to build as shared or static." +//# else +//# error "Choose whether to link against shared or static." +//# endif +//#endif diff --git a/NodeEditorPro/include/nodes/FlowScene.hpp b/NodeEditorPro/include/nodes/FlowScene.hpp new file mode 100644 index 0000000..0bd6f94 --- /dev/null +++ b/NodeEditorPro/include/nodes/FlowScene.hpp @@ -0,0 +1,168 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +#include "QUuidStdHash.hpp" +#include "Export.hpp" +#include "DataModelRegistry.hpp" +#include "TypeConverter.hpp" +#include "memory.hpp" + +namespace QtNodes +{ + + class NodeDataModel; + class FlowItemInterface; + class Node; + class NodeGraphicsObject; + class Connection; + class ConnectionGraphicsObject; + class NodeStyle; + + /// Scene holds connections and nodes. + class FlowScene + : public QGraphicsScene + { + Q_OBJECT + public: + + FlowScene(std::shared_ptr registry, + QObject* parent = Q_NULLPTR); + + FlowScene(QObject* parent = Q_NULLPTR); + + ~FlowScene(); + + public: + + std::shared_ptr + createConnection(PortType connectedPort, + Node& node, + PortIndex portIndex); + + std::shared_ptr + createConnection(Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter const& converter = TypeConverter{}); + + std::shared_ptr restoreConnection(QJsonObject const& connectionJson); + + void deleteConnection(Connection const& connection); + + Node& createNode(std::unique_ptr&& dataModel); + + Node& restoreNode(QJsonObject const& nodeJson); + + void removeNode(Node& node); + + DataModelRegistry& registry() const; + + void setRegistry(std::shared_ptr registry); + + void iterateOverNodes(std::function const& visitor); + + void iterateOverNodeData(std::function const& visitor); + + void iterateOverNodeDataDependentOrder(std::function const& visitor); + + QPointF getNodePosition(Node const& node) const; + + void setNodePosition(Node& node, QPointF const& pos) const; + + QSizeF getNodeSize(Node const& node) const; + + public: + + std::unordered_map > const& nodes() const; + + std::unordered_map > const& connections() const; + + std::vector allNodes() const; + + std::vector selectedNodes() const; + + public: + + void clearScene(); + + void save() const; + + void load(); + + QByteArray saveToMemory() const; + + void loadFromMemory(const QByteArray& data); + + Q_SIGNALS: + + /** + * @brief Node has been created but not on the scene yet. + * @see nodePlaced() + */ + void nodeCreated(Node& n); + + /** + * @brief Node has been added to the scene. + * @details Connect to this signal if need a correct position of node. + * @see nodeCreated() + */ + void nodePlaced(Node& n); + + void nodeDeleted(Node& n); + + void connectionCreated(Connection const& c); + + void connectionDeleted(Connection const& c); + + void nodeMoved(Node& n, const QPointF& newLocation); + + void nodeDoubleClicked(Node& n); + + void nodeClicked(Node& n); + + void connectionHovered(Connection& c, QPoint screenPos); + + void nodeHovered(Node& n, QPoint screenPos); + + void connectionHoverLeft(Connection& c); + + void nodeHoverLeft(Node& n); + + void nodeContextMenu(Node& n, const QPointF& pos); + + private: + + using SharedConnection = std::shared_ptr; + using UniqueNode = std::unique_ptr; + + // DO NOT reorder this member to go after the others. + // This should outlive all the connections and nodes of + // the graph, so that nodes can potentially have pointers into it, + // which is why it comes first in the class. + std::shared_ptr _registry; + + std::unordered_map _connections; + std::unordered_map _nodes; + + private Q_SLOTS: + + void setupConnectionSignals(Connection const& c); + + void sendConnectionCreatedToNodes(Connection const& c); + void sendConnectionDeletedToNodes(Connection const& c); + + }; + + Node* + locateNodeAt(QPointF scenePoint, FlowScene& scene, + QTransform const& viewTransform); +} diff --git a/NodeEditorPro/include/nodes/FlowView.hpp b/NodeEditorPro/include/nodes/FlowView.hpp new file mode 100644 index 0000000..d8b8941 --- /dev/null +++ b/NodeEditorPro/include/nodes/FlowView.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include "Export.hpp" + +namespace QtNodes +{ + + class FlowScene; + + class FlowView + : public QGraphicsView + { + Q_OBJECT + public: + + FlowView(QWidget* parent = Q_NULLPTR); + FlowView(FlowScene* scene, QWidget* parent = Q_NULLPTR); + + FlowView(const FlowView&) = delete; + FlowView operator=(const FlowView&) = delete; + + QAction* clearSelectionAction() const; + + QAction* deleteSelectionAction() const; + + void setScene(FlowScene* scene); + + public Q_SLOTS: + + void scaleUp(); + + void scaleDown(); + + void deleteSelectedNodes(); + + protected: + + void contextMenuEvent(QContextMenuEvent* event) override; + + void wheelEvent(QWheelEvent* event) override; + + void keyPressEvent(QKeyEvent* event) override; + + void keyReleaseEvent(QKeyEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + + void mouseMoveEvent(QMouseEvent* event) override; + + void mouseReleaseEvent(QMouseEvent* event) override; + + void drawBackground(QPainter* painter, const QRectF& r) override; + + void showEvent(QShowEvent* event) override; + + protected: + + FlowScene* scene(); + + private: + + QAction* _clearSelectionAction; + QAction* _deleteSelectionAction; + + QPointF _clickPos; + + FlowScene* _scene; + }; +} diff --git a/NodeEditorPro/include/nodes/FlowViewStyle.hpp b/NodeEditorPro/include/nodes/FlowViewStyle.hpp new file mode 100644 index 0000000..579afad --- /dev/null +++ b/NodeEditorPro/include/nodes/FlowViewStyle.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "Style.hpp" + +namespace QtNodes +{ + + class FlowViewStyle : public Style + { + public: + + FlowViewStyle(); + + FlowViewStyle(QString jsonText); + + public: + + static void setStyle(QString jsonText); + + private: + + + void loadJsonFile(QString fileName) override; + void saveJsonFile(QString fileName) override; + + public: + + QColor BackgroundColor; + QColor FineGridColor; + QColor CoarseGridColor; + }; +} diff --git a/NodeEditorPro/include/nodes/Node.hpp b/NodeEditorPro/include/nodes/Node.hpp new file mode 100644 index 0000000..cfbddcf --- /dev/null +++ b/NodeEditorPro/include/nodes/Node.hpp @@ -0,0 +1,133 @@ +#pragma once + + +#include +#include + +#include + +#include "PortType.hpp" + +#include "Export.hpp" +#include "NodeState.hpp" +#include "NodeGeometry.hpp" +#include "NodeData.hpp" +#include "NodeGraphicsObject.hpp" +#include "ConnectionGraphicsObject.hpp" +#include "Serializable.hpp" +#include "memory.hpp" + +namespace QtNodes +{ + + class Connection; + class ConnectionState; + class NodeGraphicsObject; + class NodeDataModel; + + class Node + : public QObject + , public Serializable + { + Q_OBJECT + + public: + + /// NodeDataModel should be an rvalue and is moved into the Node + Node(std::unique_ptr&& dataModel); + + virtual + ~Node(); + + public: + /** + * \brief Json序列化 + * \return json object + */ + QJsonObject + save() const override; + /** + * \brief Json反序列化 + * \param json json + */ + void + restore(QJsonObject const& json) override; + + public: + /** + * \brief 节点的QUuid + * \return uid + */ + QUuid + id() const; + + void reactToPossibleConnection(PortType, + NodeDataType const&, + QPointF const& scenePoint); + + void + resetReactionToConnection(); + + public: + + NodeGraphicsObject const& + nodeGraphicsObject() const; + + NodeGraphicsObject& + nodeGraphicsObject(); + + void + setGraphicsObject(std::unique_ptr&& graphics); + + NodeGeometry& + nodeGeometry(); + + NodeGeometry const& + nodeGeometry() const; + + NodeState const& + nodeState() const; + + NodeState& + nodeState(); + + NodeDataModel* + nodeDataModel() const; + + public Q_SLOTS: // data propagation + + /// Propagates incoming data to the underlying model. + void + propagateData(std::shared_ptr nodeData, + PortIndex inPortIndex, + const QUuid& connectionId) const; + + /// Fetches data from model's OUT #index port + /// and propagates it to the connection + void + onDataUpdated(PortIndex index); + + /// Propagates empty data to the attached connection. + void + onDataInvalidated(PortIndex index); + + /// update the graphic part if the size of the embeddedwidget changes + void + onNodeSizeUpdated(); + + private: + + // addressing + QUuid _uid; + + // data + std::unique_ptr _nodeDataModel; + + NodeState _nodeState; + + // painting + NodeGeometry _nodeGeometry; + + std::unique_ptr _nodeGraphicsObject; + }; +} diff --git a/NodeEditorPro/include/nodes/NodeData.hpp b/NodeEditorPro/include/nodes/NodeData.hpp new file mode 100644 index 0000000..32fc94c --- /dev/null +++ b/NodeEditorPro/include/nodes/NodeData.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "Export.hpp" + +namespace QtNodes +{ + + struct NodeDataType + { + QString id; + /** + * \brief 用于比较的名称 + */ + QString name; + + friend bool operator<(QtNodes::NodeDataType const& d1, + QtNodes::NodeDataType const& d2) + { + return d1.id < d2.id; + } + + friend bool operator==(const QtNodes::NodeDataType& d1, + const QtNodes::NodeDataType& d2) noexcept + { + return d1.id == d2.id; + } + }; + + /// Class represents data transferred between nodes. + /// @param type is used for comparing the types + /// The actual data is stored in subtypes + class NodeData + { + public: + + virtual ~NodeData() = default; + + virtual bool sameType(NodeData const& nodeData) const + { + return (this->type().id == nodeData.type().id); + } + + /** + * \brief 节点标签 + * \return 标签名字 + */ + virtual NodeDataType type() const = 0; + + }; +} diff --git a/NodeEditorPro/include/nodes/NodeDataModel.hpp b/NodeEditorPro/include/nodes/NodeDataModel.hpp new file mode 100644 index 0000000..a747f65 --- /dev/null +++ b/NodeEditorPro/include/nodes/NodeDataModel.hpp @@ -0,0 +1,206 @@ +#pragma once + + +#include +#include +#include + +#include "PortType.hpp" +#include "NodeData.hpp" +#include "Serializable.hpp" +#include "NodeGeometry.hpp" +#include "NodeStyle.hpp" +#include "NodePainterDelegate.hpp" +#include "Export.hpp" +#include "memory.hpp" + +namespace QtNodes +{ + + enum class NodeValidationState + { + Valid, + Warning, + Error + }; + + class Connection; + + class StyleCollection; + + class NodeDataModel + : public QObject, + //public QThread, + public Serializable + { + Q_OBJECT + + public: + + NodeDataModel(); + + virtual + ~NodeDataModel() = default; + + /// Caption is used in GUI + virtual QString + caption() const = 0; + + /// It is possible to hide caption in GUI + virtual bool + captionVisible() const { return true; } + + /// Port caption is used in GUI to label individual ports + virtual QString + portCaption(PortType, PortIndex) const { return QString(); } + + /// It is possible to hide port caption in GUI + virtual bool + portCaptionVisible(PortType, PortIndex) const { return false; } + + /// Name makes this model unique + virtual QString + name() const = 0; + + public: + + QJsonObject + save() const override; + + public: + /** + * \brief 定义该节点有几个输入输出端口 + * \param portType 查询的端口类型 + * \return 返回查询类型的端口个数 + */ + virtual + unsigned int nPorts(PortType portType) const = 0; + + virtual + NodeDataType dataType(PortType portType, PortIndex portIndex) const = 0; + + public: + + enum class ConnectionPolicy + { + One, + Many, + }; + + virtual + ConnectionPolicy + portOutConnectionPolicy(PortIndex) const + { + return ConnectionPolicy::Many; + } + + virtual + ConnectionPolicy + portInConnectionPolicy(PortIndex) const + { + return ConnectionPolicy::One; + } + + NodeStyle const& + nodeStyle() const; + + void + setNodeStyle(NodeStyle const& style); + + public: + + /// Triggers the algorithm + virtual + void + setInData(std::shared_ptr nodeData, + PortIndex port) = 0; + + // Use this if portInConnectionPolicy returns ConnectionPolicy::Many + virtual + void + setInData(std::shared_ptr nodeData, + PortIndex port, + const QUuid& connectionId) + { + Q_UNUSED(connectionId); + setInData(nodeData, port); + } + + virtual + std::shared_ptr + outData(PortIndex port) = 0; + + /** + * It is recommented to preform a lazy initialization for the + * embedded widget and create it inside this function, not in the + * constructor of the current model. + * + * Our Model Registry is able to shortly instantiate models in order + * to call the non-static `Model::name()`. If the embedded widget is + * allocated in the constructor but not actually embedded into some + * QGraphicsProxyWidget, we'll gonna have a dangling pointer. + */ + virtual + QWidget* + embeddedWidget() = 0; + + virtual + bool + resizable() const { return false; } + + virtual + NodeValidationState + validationState() const { return NodeValidationState::Valid; } + + virtual + QString + validationMessage() const { return QString(""); } + + virtual + NodePainterDelegate* painterDelegate() const { return nullptr; } + + public Q_SLOTS: + + virtual void + inputConnectionCreated(Connection const&) + { + } + + virtual void + inputConnectionDeleted(Connection const&) + { + //qDebug() << "inputConnectionDeleted"; + //emit sigInputConnectionDeleted(); + } + + virtual void + outputConnectionCreated(Connection const&) + { + } + + virtual void + outputConnectionDeleted(Connection const&) + { + } + + Q_SIGNALS: + + /// Triggers the updates in the nodes downstream. + void dataUpdated(PortIndex index); + + /// Triggers the propagation of the empty data downstream. + void dataInvalidated(PortIndex index); + + void computingStarted(); + + void computingFinished(); + + void embeddedWidgetSizeUpdated(); + + void sigInputConnectionDeleted();//输入连接移出信号 + + private: + + NodeStyle _nodeStyle; + }; +} diff --git a/NodeEditorPro/include/nodes/NodeGeometry.hpp b/NodeEditorPro/include/nodes/NodeGeometry.hpp new file mode 100644 index 0000000..cd3581d --- /dev/null +++ b/NodeEditorPro/include/nodes/NodeGeometry.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include +#include +#include +#include + +#include "PortType.hpp" +#include "Export.hpp" +#include "memory.hpp" + +namespace QtNodes +{ + + class NodeState; + class NodeDataModel; + class Node; + + class NodeGeometry + { + public: + + NodeGeometry(std::unique_ptr const& dataModel); + + public: + unsigned int + height() const { return _height; } + + void + setHeight(unsigned int h) { _height = h; } + + unsigned int + width() const { return _width; } + + void + setWidth(unsigned int w) { _width = w; } + + unsigned int + entryHeight() const { return _entryHeight; } + void + setEntryHeight(unsigned int h) { _entryHeight = h; } + + unsigned int + entryWidth() const { return _entryWidth; } + + void + setEntryWidth(unsigned int w) { _entryWidth = w; } + + unsigned int + spacing() const { return _spacing; } + + void + setSpacing(unsigned int s) { _spacing = s; } + + bool + hovered() const { return _hovered; } + + void + setHovered(unsigned int h) { _hovered = h; } + + unsigned int + nSources() const; + + unsigned int + nSinks() const; + + QPointF const& + draggingPos() const + { + return _draggingPos; + } + + void + setDraggingPosition(QPointF const& pos) + { + _draggingPos = pos; + } + + public: + + QRectF + entryBoundingRect() const; + + QRectF + boundingRect() const; + + /// Updates size unconditionally + void + recalculateSize() const; + + /// Updates size if the QFontMetrics is changed + void + recalculateSize(QFont const& font) const; + + // TODO removed default QTransform() + QPointF + portScenePosition(PortIndex index, + PortType portType, + QTransform const& t = QTransform()) const; + + PortIndex + checkHitScenePoint(PortType portType, + QPointF point, + QTransform const& t = QTransform()) const; + + QRect + resizeRect() const; + + /// Returns the position of a widget on the Node surface + QPointF + widgetPosition() const; + + /// Returns the maximum height a widget can be without causing the node to grow. + int + equivalentWidgetHeight() const; + + unsigned int + validationHeight() const; + + unsigned int + validationWidth() const; + + static + QPointF + calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, Node* targetNode, + PortIndex sourcePortIndex, PortType sourcePort, Node* sourceNode, + Node& newNode); + private: + + unsigned int + captionHeight() const; + + unsigned int + captionWidth() const; + + unsigned int + portWidth(PortType portType) const; + + private: + + // some variables are mutable because + // we need to change drawing metrics + // corresponding to fontMetrics + // but this doesn't change constness of Node + + mutable unsigned int _width; + mutable unsigned int _height; + unsigned int _entryWidth; + mutable unsigned int _inputPortWidth; + mutable unsigned int _outputPortWidth; + mutable unsigned int _entryHeight; + unsigned int _spacing; + + bool _hovered; + + unsigned int _nSources; + unsigned int _nSinks; + + QPointF _draggingPos; + + std::unique_ptr const& _dataModel; + + mutable QFontMetrics _fontMetrics; + mutable QFontMetrics _boldFontMetrics; + }; +} diff --git a/NodeEditorPro/include/nodes/NodeGraphicsObject.hpp b/NodeEditorPro/include/nodes/NodeGraphicsObject.hpp new file mode 100644 index 0000000..4bcbd83 --- /dev/null +++ b/NodeEditorPro/include/nodes/NodeGraphicsObject.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include + +#include "Connection.hpp" + +#include "NodeGeometry.hpp" +#include "NodeState.hpp" + +class QGraphicsProxyWidget; + +namespace QtNodes +{ + + class FlowScene; + class FlowItemEntry; + + /// Class reacts on GUI events, mouse clicks and + /// forwards painting operation. + class NodeGraphicsObject : public QGraphicsObject + { + Q_OBJECT + + public: + NodeGraphicsObject(FlowScene& scene, + Node& node); + + virtual + ~NodeGraphicsObject(); + + Node& + node(); + + Node const& + node() const; + + QRectF + boundingRect() const override; + + void + setGeometryChanged(); + + /// Visits all attached connections and corrects + /// their corresponding end points. + void + moveConnections() const; + + enum { Type = UserType + 1 }; + + int + type() const override { return Type; } + + void + lock(bool locked); + + protected: + void + paint(QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget* widget = 0) override; + + QVariant + itemChange(GraphicsItemChange change, const QVariant& value) override; + + void + mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + void + mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; + + void + mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; + + void + hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; + + void + hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; + + void + hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + + void + mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) override; + + void + contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override; + + private: + + void + embedQWidget(); + + private: + + FlowScene& _scene; + + Node& _node; + + bool _locked; + + // either nullptr or owned by parent QGraphicsItem + QGraphicsProxyWidget* _proxyWidget; + }; +} diff --git a/NodeEditorPro/include/nodes/NodePainterDelegate.hpp b/NodeEditorPro/include/nodes/NodePainterDelegate.hpp new file mode 100644 index 0000000..640ff44 --- /dev/null +++ b/NodeEditorPro/include/nodes/NodePainterDelegate.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "NodeGeometry.hpp" +#include "NodeDataModel.hpp" +#include "Export.hpp" + +namespace QtNodes { + + /// Class to allow for custom painting + class NodePainterDelegate + { + + public: + + virtual + ~NodePainterDelegate() = default; + + virtual void + paint(QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model) = 0; + }; +} diff --git a/NodeEditorPro/include/nodes/NodeState.hpp b/NodeEditorPro/include/nodes/NodeState.hpp new file mode 100644 index 0000000..8b2db22 --- /dev/null +++ b/NodeEditorPro/include/nodes/NodeState.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +#include + +#include "Export.hpp" + +#include "PortType.hpp" +#include "NodeData.hpp" +#include "memory.hpp" + +namespace QtNodes +{ + + class Connection; + class NodeDataModel; + + /// Contains vectors of connected input and output connections. + /// Stores bool for reacting on hovering connections + class NodeState + { + public: + enum ReactToConnectionState + { + REACTING, + NOT_REACTING + }; + + public: + + NodeState(std::unique_ptr const& model); + + public: + + using ConnectionPtrSet = + std::unordered_map; + + /// Returns vector of connections ID. + /// Some of them can be empty (null) + std::vector const& + getEntries(PortType) const; + + std::vector& + getEntries(PortType); + + ConnectionPtrSet + connections(PortType portType, PortIndex portIndex) const; + + void + setConnection(PortType portType, + PortIndex portIndex, + Connection& connection); + + void + eraseConnection(PortType portType, + PortIndex portIndex, + QUuid id); + + ReactToConnectionState + reaction() const; + + PortType + reactingPortType() const; + + NodeDataType + reactingDataType() const; + + void + setReaction(ReactToConnectionState reaction, + PortType reactingPortType = PortType::None, + + NodeDataType reactingDataType = + NodeDataType()); + + bool + isReacting() const; + + void + setResizing(bool resizing); + + bool + resizing() const; + + private: + + std::vector _inConnections; + std::vector _outConnections; + + ReactToConnectionState _reaction; + PortType _reactingPortType; + NodeDataType _reactingDataType; + + bool _resizing; + }; +} diff --git a/NodeEditorPro/include/nodes/NodeStyle.hpp b/NodeEditorPro/include/nodes/NodeStyle.hpp new file mode 100644 index 0000000..8e0cf49 --- /dev/null +++ b/NodeEditorPro/include/nodes/NodeStyle.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "Style.hpp" + +namespace QtNodes +{ + + class NodeStyle : public Style + { + public: + + NodeStyle(); + + NodeStyle(QString jsonText); + + public: + + static void setNodeStyle(QString fileName); + + private: + + void loadJsonFile(QString fileName) override; + void saveJsonFile(QString fileName) override; + + public: + + QColor NormalBoundaryColor; + QColor SelectedBoundaryColor; + QColor BackgroundColor; + QColor TitleColor; + QColor GradientColor0; + QColor GradientColor1; + QColor GradientColor2; + QColor GradientColor3; + QColor ShadowColor; + QColor FontColor; + QColor FontColorFaded; + + QColor ConnectionPointColor; + QColor FilledConnectionPointColor; + + QColor WarningColor; + QColor ErrorColor; + + float PenWidth; + float HoveredPenWidth; + + float ConnectionPointDiameter; + + float Opacity; + }; +} diff --git a/NodeEditorPro/include/nodes/OperatingSystem.hpp b/NodeEditorPro/include/nodes/OperatingSystem.hpp new file mode 100644 index 0000000..2682373 --- /dev/null +++ b/NodeEditorPro/include/nodes/OperatingSystem.hpp @@ -0,0 +1,78 @@ +#pragma once + +#if \ + defined (__CYGWIN__) || \ + defined (__CYGWIN32__) +# define NODE_EDITOR_PLATFORM "Cygwin" +# define NODE_EDITOR_PLATFORM_CYGWIN +# define NODE_EDITOR_PLATFORM_UNIX +# define NODE_EDITOR_PLATFORM_WINDOWS +#elif \ + defined (_WIN16) || \ + defined (_WIN32) || \ + defined (_WIN64) || \ + defined (__WIN32__) || \ + defined (__TOS_WIN__) || \ + defined (__WINDOWS__) +# define NODE_EDITOR_PLATFORM "Windows" +# define NODE_EDITOR_PLATFORM_WINDOWS +#elif \ + defined (macintosh) || \ + defined (Macintosh) || \ + defined (__TOS_MACOS__) || \ + (defined (__APPLE__) && defined (__MACH__)) +# define NODE_EDITOR_PLATFORM "Mac" +# define NODE_EDITOR_PLATFORM_MAC +# define NODE_EDITOR_PLATFORM_UNIX +#elif \ + defined (linux) || \ + defined (__linux) || \ + defined (__linux__) || \ + defined (__TOS_LINUX__) +# define NODE_EDITOR_PLATFORM "Linux" +# define NODE_EDITOR_PLATFORM_LINUX +# define NODE_EDITOR_PLATFORM_UNIX +#elif \ + defined (__FreeBSD__) || \ + defined (__OpenBSD__) || \ + defined (__NetBSD__) || \ + defined (__bsdi__) || \ + defined (__DragonFly__) +# define NODE_EDITOR_PLATFORM "BSD" +# define NODE_EDITOR_PLATFORM_BSD +# define NODE_EDITOR_PLATFORM_UNIX +#elif \ + defined (sun) || \ + defined (__sun) +# define NODE_EDITOR_PLATFORM "Solaris" +# define NODE_EDITOR_PLATFORM_SOLARIS +# define NODE_EDITOR_PLATFORM_UNIX +#elif \ + defined (_AIX) || \ + defined (__TOS_AIX__) +# define NODE_EDITOR_PLATFORM "AIX" +# define NODE_EDITOR_PLATFORM_AIX +# define NODE_EDITOR_PLATFORM_UNIX +#elif \ + defined (hpux) || \ + defined (_hpux) || \ + defined (__hpux) +# define NODE_EDITOR_PLATFORM "HPUX" +# define NODE_EDITOR_PLATFORM_HPUX +# define NODE_EDITOR_PLATFORM_UNIX +#elif \ + defined (__QNX__) +# define NODE_EDITOR_PLATFORM "QNX" +# define NODE_EDITOR_PLATFORM_QNX +# define NODE_EDITOR_PLATFORM_UNIX +#elif \ + defined (unix) || \ + defined (__unix) || \ + defined (__unix__) +# define NODE_EDITOR_PLATFORM "Unix" +# define NODE_EDITOR_PLATFORM_UNIX +#endif + +#ifndef NODE_EDITOR_PLATFORM +# error "Current platform is not supported." +#endif diff --git a/NodeEditorPro/include/nodes/PortType.hpp b/NodeEditorPro/include/nodes/PortType.hpp new file mode 100644 index 0000000..aa4cc97 --- /dev/null +++ b/NodeEditorPro/include/nodes/PortType.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +namespace QtNodes +{ + + enum class PortType + { + None, + In, + Out + }; + + static const int INVALID = -1; + + using PortIndex = int; + + struct Port + { + PortType type; + + PortIndex index; + + Port() + : type(PortType::None) + , index(INVALID) + {} + + Port(PortType t, PortIndex i) + : type(t) + , index(i) + {} + + bool + indexIsValid() { return index != INVALID; } + + bool + portTypeIsValid() { return type != PortType::None; } + }; + + //using PortAddress = std::pair; + + inline + PortType + oppositePort(PortType port) + { + PortType result = PortType::None; + + switch (port) + { + case PortType::In: + result = PortType::Out; + break; + + case PortType::Out: + result = PortType::In; + break; + + default: + break; + } + + return result; + } +} diff --git a/NodeEditorPro/include/nodes/QStringStdHash.hpp b/NodeEditorPro/include/nodes/QStringStdHash.hpp new file mode 100644 index 0000000..9cb53f7 --- /dev/null +++ b/NodeEditorPro/include/nodes/QStringStdHash.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + +// As of 5.14 there is a specialization std::hash + +#include + +#include +#include + +namespace std +{ + template<> + struct hash + { + inline std::size_t + operator()(QString const& s) const + { + return qHash(s); + } + }; +} + +#endif diff --git a/NodeEditorPro/include/nodes/QUuidStdHash.hpp b/NodeEditorPro/include/nodes/QUuidStdHash.hpp new file mode 100644 index 0000000..829c024 --- /dev/null +++ b/NodeEditorPro/include/nodes/QUuidStdHash.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include +#include + +namespace std +{ + template<> + struct hash + { + inline + std::size_t + operator()(QUuid const& uid) const + { + return qHash(uid); + } + }; +} + diff --git a/NodeEditorPro/include/nodes/Serializable.hpp b/NodeEditorPro/include/nodes/Serializable.hpp new file mode 100644 index 0000000..984f5af --- /dev/null +++ b/NodeEditorPro/include/nodes/Serializable.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace QtNodes +{ + + class Serializable + { + public: + + virtual + ~Serializable() = default; + + virtual + QJsonObject + save() const = 0; + + virtual void + restore(QJsonObject const& /*p*/) {} + }; +} diff --git a/NodeEditorPro/include/nodes/Style.hpp b/NodeEditorPro/include/nodes/Style.hpp new file mode 100644 index 0000000..4ae51a6 --- /dev/null +++ b/NodeEditorPro/include/nodes/Style.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace QtNodes +{ + + class Style + { + public: + + virtual + ~Style() = default; + + private: + + virtual void + loadJsonFile(QString fileName) = 0; + virtual void + saveJsonFile(QString fileName) = 0; + }; + +} diff --git a/NodeEditorPro/include/nodes/StyleCollection.hpp b/NodeEditorPro/include/nodes/StyleCollection.hpp new file mode 100644 index 0000000..14b246a --- /dev/null +++ b/NodeEditorPro/include/nodes/StyleCollection.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "NodeStyle.hpp" +#include "ConnectionStyle.hpp" +#include "FlowViewStyle.hpp" +#include "Export.hpp" + +namespace QtNodes +{ + + class StyleCollection + { + public: + + static + NodeStyle const& + nodeStyle(); + + static + ConnectionStyle const& + connectionStyle(); + + static + FlowViewStyle const& + flowViewStyle(); + + public: + + static + void + setNodeStyle(NodeStyle); + + static + void + setConnectionStyle(ConnectionStyle); + + static + void + setFlowViewStyle(FlowViewStyle); + + private: + + StyleCollection() = default; + + StyleCollection(StyleCollection const&) = delete; + + StyleCollection& + operator=(StyleCollection const&) = delete; + + static + StyleCollection& + instance(); + + private: + + NodeStyle _nodeStyle; + + ConnectionStyle _connectionStyle; + + FlowViewStyle _flowViewStyle; + }; +} diff --git a/NodeEditorPro/include/nodes/TypeConverter.hpp b/NodeEditorPro/include/nodes/TypeConverter.hpp new file mode 100644 index 0000000..c66f623 --- /dev/null +++ b/NodeEditorPro/include/nodes/TypeConverter.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "NodeData.hpp" +#include "memory.hpp" + +#include + +namespace QtNodes +{ + + using SharedNodeData = std::shared_ptr; + + // a function taking in NodeData and returning NodeData + using TypeConverter = + std::function; + + // data-type-in, data-type-out + using TypeConverterId = + std::pair; + + struct TypeConverterIdHash + { + std::size_t operator()(const QtNodes::TypeConverterId& converter) const noexcept + { + return qHash(converter.first.id) + ^ qHash(converter.second.id); + } + }; + +} diff --git a/NodeEditorPro/include/nodes/memory.hpp b/NodeEditorPro/include/nodes/memory.hpp new file mode 100644 index 0000000..46022d8 --- /dev/null +++ b/NodeEditorPro/include/nodes/memory.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace QtNodes +{ + namespace detail { +#if (!defined(_MSC_VER) && (__cplusplus < 201300)) || \ + ( defined(_MSC_VER) && (_MSC_VER < 1800)) + //_MSC_VER == 1800 is Visual Studio 2013, which is already somewhat C++14 compilant, + // and it has make_unique in it's standard library implementation + template + std::unique_ptr make_unique(Args&&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } +#else + template + std::unique_ptr make_unique(Args&&... args) + { + return std::make_unique(std::forward(args)...); + } +#endif + } +} diff --git a/NodeEditorPro/resources/DefaultStyle - 副本.json b/NodeEditorPro/resources/DefaultStyle - 副本.json new file mode 100644 index 0000000..aa5899d --- /dev/null +++ b/NodeEditorPro/resources/DefaultStyle - 副本.json @@ -0,0 +1,44 @@ +{ + "FlowViewStyle": { + "BackgroundColor": [ 53, 53, 53 ], + "FineGridColor": [ 60, 60, 60 ], + "CoarseGridColor": [ 25, 25, 25 ] + }, + "NodeStyle": { + "NormalBoundaryColor": [ 255, 255, 255 ], + "SelectedBoundaryColor": [ 255, 165, 0 ], + "BackgroundColor": [ 34, 34, 34 ], + "TitleColor": [ 253, 204, 82 ], + "GradientColor0": "gray", + "GradientColor1": [ 80, 80, 80 ], + "GradientColor2": [ 64, 64, 64 ], + "GradientColor3": [ 58, 58, 58 ], + "ShadowColor": [ 20, 20, 20 ], + "FontColor": "white", + "FontColorFaded": "gray", + "ConnectionPointColor": [ 169, 169, 169 ], + "FilledConnectionPointColor": "cyan", + "ErrorColor": "red", + "WarningColor": [ 128, 128, 0 ], + + "PenWidth": 1.0, + "HoveredPenWidth": 1.5, + + "ConnectionPointDiameter": 8.0, + + "Opacity": 0.8 + }, + "ConnectionStyle": { + "ConstructionColor": "gray", + "NormalColor": "darkcyan", + "SelectedColor": [ 100, 100, 100 ], + "SelectedHaloColor": "orange", + "HoveredColor": "lightcyan", + + "LineWidth": 3.0, + "ConstructionLineWidth": 2.0, + "PointDiameter": 10.0, + + "UseDataDefinedColors": false + } +} diff --git a/NodeEditorPro/resources/DefaultStyle.json b/NodeEditorPro/resources/DefaultStyle.json new file mode 100644 index 0000000..aa5899d --- /dev/null +++ b/NodeEditorPro/resources/DefaultStyle.json @@ -0,0 +1,44 @@ +{ + "FlowViewStyle": { + "BackgroundColor": [ 53, 53, 53 ], + "FineGridColor": [ 60, 60, 60 ], + "CoarseGridColor": [ 25, 25, 25 ] + }, + "NodeStyle": { + "NormalBoundaryColor": [ 255, 255, 255 ], + "SelectedBoundaryColor": [ 255, 165, 0 ], + "BackgroundColor": [ 34, 34, 34 ], + "TitleColor": [ 253, 204, 82 ], + "GradientColor0": "gray", + "GradientColor1": [ 80, 80, 80 ], + "GradientColor2": [ 64, 64, 64 ], + "GradientColor3": [ 58, 58, 58 ], + "ShadowColor": [ 20, 20, 20 ], + "FontColor": "white", + "FontColorFaded": "gray", + "ConnectionPointColor": [ 169, 169, 169 ], + "FilledConnectionPointColor": "cyan", + "ErrorColor": "red", + "WarningColor": [ 128, 128, 0 ], + + "PenWidth": 1.0, + "HoveredPenWidth": 1.5, + + "ConnectionPointDiameter": 8.0, + + "Opacity": 0.8 + }, + "ConnectionStyle": { + "ConstructionColor": "gray", + "NormalColor": "darkcyan", + "SelectedColor": [ 100, 100, 100 ], + "SelectedHaloColor": "orange", + "HoveredColor": "lightcyan", + + "LineWidth": 3.0, + "ConstructionLineWidth": 2.0, + "PointDiameter": 10.0, + + "UseDataDefinedColors": false + } +} diff --git a/NodeEditorPro/resources/convert.png b/NodeEditorPro/resources/convert.png new file mode 100644 index 0000000..f379788 Binary files /dev/null and b/NodeEditorPro/resources/convert.png differ diff --git a/NodeEditorPro/resources/logo.png b/NodeEditorPro/resources/logo.png new file mode 100644 index 0000000..bb24b44 Binary files /dev/null and b/NodeEditorPro/resources/logo.png differ diff --git a/NodeEditorPro/resources/resources.qrc b/NodeEditorPro/resources/resources.qrc new file mode 100644 index 0000000..51dae74 --- /dev/null +++ b/NodeEditorPro/resources/resources.qrc @@ -0,0 +1,7 @@ + + + DefaultStyle.json + convert.png + logo.png + + diff --git a/NodeEditorPro/showcase/ReduceDomain.gif b/NodeEditorPro/showcase/ReduceDomain.gif new file mode 100644 index 0000000..2625287 Binary files /dev/null and b/NodeEditorPro/showcase/ReduceDomain.gif differ diff --git a/NodeEditorPro/showcase/ReduceDomain.mp4 b/NodeEditorPro/showcase/ReduceDomain.mp4 new file mode 100644 index 0000000..9584530 Binary files /dev/null and b/NodeEditorPro/showcase/ReduceDomain.mp4 differ diff --git a/NodeEditorPro/showcase/draw_shape_view.png b/NodeEditorPro/showcase/draw_shape_view.png new file mode 100644 index 0000000..c17f144 Binary files /dev/null and b/NodeEditorPro/showcase/draw_shape_view.png differ diff --git a/NodeEditorPro/showcase/reduceNode.png b/NodeEditorPro/showcase/reduceNode.png new file mode 100644 index 0000000..a044dd7 Binary files /dev/null and b/NodeEditorPro/showcase/reduceNode.png differ diff --git a/NodeEditorPro/showcase/selectBallTest.png b/NodeEditorPro/showcase/selectBallTest.png new file mode 100644 index 0000000..4be5b9d Binary files /dev/null and b/NodeEditorPro/showcase/selectBallTest.png differ diff --git a/NodeEditorPro/showcase/selectRegionNode.png b/NodeEditorPro/showcase/selectRegionNode.png new file mode 100644 index 0000000..4d2b993 Binary files /dev/null and b/NodeEditorPro/showcase/selectRegionNode.png differ diff --git a/NodeEditorPro/showcase/showcase1.gif b/NodeEditorPro/showcase/showcase1.gif new file mode 100644 index 0000000..725b67e Binary files /dev/null and b/NodeEditorPro/showcase/showcase1.gif differ diff --git a/NodeEditorPro/showcase/showcase2.mp4 b/NodeEditorPro/showcase/showcase2.mp4 new file mode 100644 index 0000000..2838749 Binary files /dev/null and b/NodeEditorPro/showcase/showcase2.mp4 differ diff --git a/NodeEditorPro/showcase/showcase3.gif b/NodeEditorPro/showcase/showcase3.gif new file mode 100644 index 0000000..42a0842 Binary files /dev/null and b/NodeEditorPro/showcase/showcase3.gif differ diff --git a/NodeEditorPro/showcase/showcase4.gif b/NodeEditorPro/showcase/showcase4.gif new file mode 100644 index 0000000..748b4f9 Binary files /dev/null and b/NodeEditorPro/showcase/showcase4.gif differ diff --git a/NodeEditorPro/showcase/showcase5.png b/NodeEditorPro/showcase/showcase5.png new file mode 100644 index 0000000..b9ef50e Binary files /dev/null and b/NodeEditorPro/showcase/showcase5.png differ diff --git a/NodeEditorPro/showcase/showcase6.mp4 b/NodeEditorPro/showcase/showcase6.mp4 new file mode 100644 index 0000000..59e759a Binary files /dev/null and b/NodeEditorPro/showcase/showcase6.mp4 differ diff --git a/NodeEditorPro/src/Connection.cpp b/NodeEditorPro/src/Connection.cpp new file mode 100644 index 0000000..25c6524 --- /dev/null +++ b/NodeEditorPro/src/Connection.cpp @@ -0,0 +1,450 @@ +#include "Connection.hpp" + +#include +#include + +#include +#include + +#include "Node.hpp" +#include "FlowScene.hpp" +#include "FlowView.hpp" + +#include "NodeGeometry.hpp" +#include "NodeGraphicsObject.hpp" +#include "NodeDataModel.hpp" + +#include "ConnectionState.hpp" +#include "ConnectionGeometry.hpp" +#include "ConnectionGraphicsObject.hpp" + +using QtNodes::Connection; +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::ConnectionState; +using QtNodes::Node; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::ConnectionGraphicsObject; +using QtNodes::ConnectionGeometry; +using QtNodes::TypeConverter; + +Connection:: +Connection(PortType portType, + Node& node, + PortIndex portIndex) + : _uid(QUuid::createUuid()) + , _outPortIndex(INVALID) + , _inPortIndex(INVALID) + , _connectionState() +{ + setNodeToPort(node, portType, portIndex); + + setRequiredPort(oppositePort(portType)); +} + + +Connection:: +Connection(Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter typeConverter) + : _uid(QUuid::createUuid()) + , _outNode(&nodeOut) + , _inNode(&nodeIn) + , _outPortIndex(portIndexOut) + , _inPortIndex(portIndexIn) + , _connectionState() + , _converter(std::move(typeConverter)) +{ + setNodeToPort(nodeIn, PortType::In, portIndexIn); + setNodeToPort(nodeOut, PortType::Out, portIndexOut); +} + + +Connection:: +~Connection() +{ + if (complete()) + { + connectionMadeIncomplete(*this); + } + + if (_inNode) + { + _inNode->nodeGraphicsObject().update(); + } + + if (_outNode) + { + propagateEmptyData(); + _outNode->nodeGraphicsObject().update(); + } +} + + +QJsonObject +Connection:: +save() const +{ + QJsonObject connectionJson; + + if (_inNode && _outNode) + { + connectionJson["in_id"] = _inNode->id().toString(); + connectionJson["in_index"] = _inPortIndex; + + connectionJson["out_id"] = _outNode->id().toString(); + connectionJson["out_index"] = _outPortIndex; + + if (_converter) + { + auto getTypeJson = [this](PortType type) + { + QJsonObject typeJson; + NodeDataType nodeType = this->dataType(type); + typeJson["id"] = nodeType.id; + typeJson["name"] = nodeType.name; + + return typeJson; + }; + + QJsonObject converterTypeJson; + + converterTypeJson["in"] = getTypeJson(PortType::In); + converterTypeJson["out"] = getTypeJson(PortType::Out); + + connectionJson["converter"] = converterTypeJson; + } + } + + return connectionJson; +} + + +QUuid +Connection:: +id() const +{ + return _uid; +} + + +bool +Connection:: +complete() const +{ + return _inNode != nullptr && _outNode != nullptr; +} + + +void +Connection:: +setRequiredPort(PortType dragging) +{ + _connectionState.setRequiredPort(dragging); + + switch (dragging) + { + case PortType::Out: + _outNode = nullptr; + _outPortIndex = INVALID; + break; + + case PortType::In: + _inNode = nullptr; + _inPortIndex = INVALID; + break; + + default: + break; + } +} + + +PortType +Connection:: +requiredPort() const +{ + return _connectionState.requiredPort(); +} + + +void +Connection:: +setGraphicsObject(std::unique_ptr&& graphics) +{ + _connectionGraphicsObject = std::move(graphics); + + // This function is only called when the ConnectionGraphicsObject + // is newly created. At this moment both end coordinates are (0, 0) + // in Connection G.O. coordinates. The position of the whole + // Connection G. O. in scene coordinate system is also (0, 0). + // By moving the whole object to the Node Port position + // we position both connection ends correctly. + + if (requiredPort() != PortType::None) + { + + PortType attachedPort = oppositePort(requiredPort()); + + PortIndex attachedPortIndex = getPortIndex(attachedPort); + + auto node = getNode(attachedPort); + + QTransform nodeSceneTransform = + node->nodeGraphicsObject().sceneTransform(); + + QPointF pos = node->nodeGeometry().portScenePosition(attachedPortIndex, + attachedPort, + nodeSceneTransform); + + _connectionGraphicsObject->setPos(pos); + } + + _connectionGraphicsObject->move(); +} + + + +PortIndex +Connection:: +getPortIndex(PortType portType) const +{ + PortIndex result = INVALID; + + switch (portType) + { + case PortType::In: + result = _inPortIndex; + break; + + case PortType::Out: + result = _outPortIndex; + + break; + + default: + break; + } + + return result; +} + + +void +Connection:: +setNodeToPort(Node& node, + PortType portType, + PortIndex portIndex) +{ + bool wasIncomplete = !complete(); + + auto& nodeWeak = getNode(portType); + + nodeWeak = &node; + + if (portType == PortType::Out) + _outPortIndex = portIndex; + else + _inPortIndex = portIndex; + + _connectionState.setNoRequiredPort(); + + updated(*this); + if (complete() && wasIncomplete) { + connectionCompleted(*this); + } +} + + +void +Connection:: +removeFromNodes() const +{ + if (_inNode) + _inNode->nodeState().eraseConnection(PortType::In, _inPortIndex, id()); + + if (_outNode) + _outNode->nodeState().eraseConnection(PortType::Out, _outPortIndex, id()); +} + + +ConnectionGraphicsObject& +Connection:: +getConnectionGraphicsObject() const +{ + return *_connectionGraphicsObject; +} + + +ConnectionState& +Connection:: +connectionState() +{ + return _connectionState; +} + + +ConnectionState const& +Connection:: +connectionState() const +{ + return _connectionState; +} + + +ConnectionGeometry& +Connection:: +connectionGeometry() +{ + return _connectionGeometry; +} + + +ConnectionGeometry const& +Connection:: +connectionGeometry() const +{ + return _connectionGeometry; +} + + +Node* +Connection:: +getNode(PortType portType) const +{ + switch (portType) + { + case PortType::In: + return _inNode; + break; + + case PortType::Out: + return _outNode; + break; + + default: + // not possible + break; + } + return nullptr; +} + + +Node*& +Connection:: +getNode(PortType portType) +{ + switch (portType) + { + case PortType::In: + return _inNode; + break; + + case PortType::Out: + return _outNode; + break; + + default: + // not possible + break; + } + Q_UNREACHABLE(); +} + + +void +Connection:: +clearNode(PortType portType) +{ + if (complete()) + { + connectionMadeIncomplete(*this); + } + + getNode(portType) = nullptr; + + if (portType == PortType::In) + _inPortIndex = INVALID; + else + _outPortIndex = INVALID; +} + + +NodeDataType +Connection:: +dataType(PortType portType) const +{ + if (_inNode && _outNode) + { + auto const& model = (portType == PortType::In) ? + _inNode->nodeDataModel() : + _outNode->nodeDataModel(); + PortIndex index = (portType == PortType::In) ? + _inPortIndex : + _outPortIndex; + + return model->dataType(portType, index); + } + else + { + Node* validNode; + PortIndex index = INVALID; + + if ((validNode = _inNode)) + { + index = _inPortIndex; + portType = PortType::In; + } + else if ((validNode = _outNode)) + { + index = _outPortIndex; + portType = PortType::Out; + } + + if (validNode) + { + auto const& model = validNode->nodeDataModel(); + + return model->dataType(portType, index); + } + } + + Q_UNREACHABLE(); +} + + +void +Connection:: +setTypeConverter(TypeConverter converter) +{ + _converter = std::move(converter); +} + + +void +Connection:: +propagateData(std::shared_ptr nodeData) const +{ + if (_inNode) + { + if (_converter) + { + nodeData = _converter(nodeData); + } + + _inNode->propagateData(nodeData, _inPortIndex, id()); + } +} + + +void +Connection:: +propagateEmptyData() const +{ + std::shared_ptr emptyData; + + propagateData(emptyData); +} diff --git a/NodeEditorPro/src/ConnectionBlurEffect.cpp b/NodeEditorPro/src/ConnectionBlurEffect.cpp new file mode 100644 index 0000000..f7e74da --- /dev/null +++ b/NodeEditorPro/src/ConnectionBlurEffect.cpp @@ -0,0 +1,27 @@ +#include "ConnectionBlurEffect.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionPainter.hpp" + +using QtNodes::ConnectionBlurEffect; +using QtNodes::ConnectionGraphicsObject; + +ConnectionBlurEffect:: +ConnectionBlurEffect(ConnectionGraphicsObject*) +{ + // +} + + +void +ConnectionBlurEffect:: +draw(QPainter* painter) +{ + QGraphicsBlurEffect::draw(painter); + + //ConnectionPainter::paint(painter, + //_object->connectionGeometry(), + //_object->connectionState()); + + //_item->paint(painter, nullptr, nullptr); +} diff --git a/NodeEditorPro/src/ConnectionBlurEffect.hpp b/NodeEditorPro/src/ConnectionBlurEffect.hpp new file mode 100644 index 0000000..4a8d16b --- /dev/null +++ b/NodeEditorPro/src/ConnectionBlurEffect.hpp @@ -0,0 +1,22 @@ +#include + +#include + +namespace QtNodes +{ + + class ConnectionGraphicsObject; + + class ConnectionBlurEffect : public QGraphicsBlurEffect + { + + public: + + ConnectionBlurEffect(ConnectionGraphicsObject* item); + + void + draw(QPainter* painter) override; + + private: + }; +} diff --git a/NodeEditorPro/src/ConnectionGeometry.cpp b/NodeEditorPro/src/ConnectionGeometry.cpp new file mode 100644 index 0000000..0216258 --- /dev/null +++ b/NodeEditorPro/src/ConnectionGeometry.cpp @@ -0,0 +1,131 @@ +#include "ConnectionGeometry.hpp" + +#include + +#include "StyleCollection.hpp" + +using QtNodes::ConnectionGeometry; +using QtNodes::PortType; + +ConnectionGeometry:: +ConnectionGeometry() + : _in(0, 0) + , _out(0, 0) + //, _animationPhase(0) + , _lineWidth(3.0) + , _hovered(false) +{ } + +QPointF const& +ConnectionGeometry:: +getEndPoint(PortType portType) const +{ + Q_ASSERT(portType != PortType::None); + + return (portType == PortType::Out ? + _out : + _in); +} + + +void +ConnectionGeometry:: +setEndPoint(PortType portType, QPointF const& point) +{ + switch (portType) + { + case PortType::Out: + _out = point; + break; + + case PortType::In: + _in = point; + break; + + default: + break; + } +} + + +void +ConnectionGeometry:: +moveEndPoint(PortType portType, QPointF const& offset) +{ + switch (portType) + { + case PortType::Out: + _out += offset; + break; + + case PortType::In: + _in += offset; + break; + + default: + break; + } +} + + +QRectF +ConnectionGeometry:: +boundingRect() const +{ + auto points = pointsC1C2(); + + QRectF basicRect = QRectF(_out, _in).normalized(); + + QRectF c1c2Rect = QRectF(points.first, points.second).normalized(); + + auto const& connectionStyle = + StyleCollection::connectionStyle(); + + float const diam = connectionStyle.pointDiameter(); + + QRectF commonRect = basicRect.united(c1c2Rect); + + QPointF const cornerOffset(diam, diam); + + commonRect.setTopLeft(commonRect.topLeft() - cornerOffset); + commonRect.setBottomRight(commonRect.bottomRight() + 2 * cornerOffset); + + return commonRect; +} + + +std::pair +ConnectionGeometry:: +pointsC1C2() const +{ + const double defaultOffset = 200; + + double xDistance = _in.x() - _out.x(); + + double horizontalOffset = qMin(defaultOffset, std::abs(xDistance)); + + double verticalOffset = 0; + + double ratioX = 0.5; + + if (xDistance <= 0) + { + double yDistance = _in.y() - _out.y() + 20; + + double vector = yDistance < 0 ? -1.0 : 1.0; + + verticalOffset = qMin(defaultOffset, std::abs(yDistance)) * vector; + + ratioX = 1.0; + } + + horizontalOffset *= ratioX; + + QPointF c1(_out.x() + horizontalOffset, + _out.y() + verticalOffset); + + QPointF c2(_in.x() - horizontalOffset, + _in.y() - verticalOffset); + + return std::make_pair(c1, c2); +} diff --git a/NodeEditorPro/src/ConnectionGraphicsObject.cpp b/NodeEditorPro/src/ConnectionGraphicsObject.cpp new file mode 100644 index 0000000..46013ba --- /dev/null +++ b/NodeEditorPro/src/ConnectionGraphicsObject.cpp @@ -0,0 +1,261 @@ +#include "ConnectionGraphicsObject.hpp" + +#include +#include +#include +#include +#include + +#include "FlowScene.hpp" + +#include "Connection.hpp" +#include "ConnectionGeometry.hpp" +#include "ConnectionPainter.hpp" +#include "ConnectionState.hpp" +#include "ConnectionBlurEffect.hpp" + +#include "NodeGraphicsObject.hpp" + +#include "NodeConnectionInteraction.hpp" + +#include "Node.hpp" + +using QtNodes::ConnectionGraphicsObject; +using QtNodes::Connection; +using QtNodes::FlowScene; + +ConnectionGraphicsObject:: +ConnectionGraphicsObject(FlowScene& scene, + Connection& connection) + : _scene(scene) + , _connection(connection) +{ + _scene.addItem(this); + + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + + setAcceptHoverEvents(true); + + // addGraphicsEffect(); + + setZValue(-1.0); +} + + +ConnectionGraphicsObject:: +~ConnectionGraphicsObject() +{ + _scene.removeItem(this); +} + + +QtNodes::Connection& +ConnectionGraphicsObject:: +connection() +{ + return _connection; +} + + +QRectF +ConnectionGraphicsObject:: +boundingRect() const +{ + return _connection.connectionGeometry().boundingRect(); +} + + +QPainterPath +ConnectionGraphicsObject:: +shape() const +{ +#ifdef DEBUG_DRAWING + + //QPainterPath path; + + //path.addRect(boundingRect()); + //return path; + +#else + auto const& geom = + _connection.connectionGeometry(); + + return ConnectionPainter::getPainterStroke(geom); + +#endif +} + + +void +ConnectionGraphicsObject:: +setGeometryChanged() +{ + prepareGeometryChange(); +} + + +void +ConnectionGraphicsObject:: +move() +{ + for (PortType portType : { PortType::In, PortType::Out }) + { + if (auto node = _connection.getNode(portType)) + { + auto const& nodeGraphics = node->nodeGraphicsObject(); + + auto const& nodeGeom = node->nodeGeometry(); + + QPointF scenePos = + nodeGeom.portScenePosition(_connection.getPortIndex(portType), + portType, + nodeGraphics.sceneTransform()); + + QTransform sceneTransform = this->sceneTransform(); + + QPointF connectionPos = sceneTransform.inverted().map(scenePos); + + _connection.connectionGeometry().setEndPoint(portType, + connectionPos); + + _connection.getConnectionGraphicsObject().setGeometryChanged(); + _connection.getConnectionGraphicsObject().update(); + } + } + +} + +void ConnectionGraphicsObject::lock(bool locked) +{ + setFlag(QGraphicsItem::ItemIsMovable, !locked); + setFlag(QGraphicsItem::ItemIsFocusable, !locked); + setFlag(QGraphicsItem::ItemIsSelectable, !locked); +} + + +void +ConnectionGraphicsObject:: +paint(QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget*) +{ + painter->setClipRect(option->exposedRect); + + ConnectionPainter::paint(painter, + _connection); +} + + +void +ConnectionGraphicsObject:: +mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsItem::mousePressEvent(event); + //event->ignore(); +} + + +void +ConnectionGraphicsObject:: +mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + prepareGeometryChange(); + + auto view = static_cast(event->widget()); + auto node = locateNodeAt(event->scenePos(), + _scene, + view->transform()); + + auto& state = _connection.connectionState(); + + state.interactWithNode(node); + if (node) + { + node->reactToPossibleConnection(state.requiredPort(), + _connection.dataType(oppositePort(state.requiredPort())), + event->scenePos()); + } + + //------------------- + + QPointF offset = event->pos() - event->lastPos(); + + auto requiredPort = _connection.requiredPort(); + + if (requiredPort != PortType::None) + { + _connection.connectionGeometry().moveEndPoint(requiredPort, offset); + } + + //------------------- + + update(); + + event->accept(); +} + + +void +ConnectionGraphicsObject:: +mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + ungrabMouse(); + event->accept(); + + auto node = locateNodeAt(event->scenePos(), _scene, + _scene.views()[0]->transform()); + + NodeConnectionInteraction interaction(*node, _connection, _scene); + + if (node && interaction.tryConnect()) + { + node->resetReactionToConnection(); + } + + if (_connection.connectionState().requiresPort()) + { + _scene.deleteConnection(_connection); + } +} + + +void +ConnectionGraphicsObject:: +hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + _connection.connectionGeometry().setHovered(true); + + update(); + _scene.connectionHovered(connection(), event->screenPos()); + event->accept(); +} + + +void +ConnectionGraphicsObject:: +hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + _connection.connectionGeometry().setHovered(false); + + update(); + _scene.connectionHoverLeft(connection()); + event->accept(); +} + + +void +ConnectionGraphicsObject:: +addGraphicsEffect() +{ + auto effect = new QGraphicsBlurEffect; + + effect->setBlurRadius(5); + setGraphicsEffect(effect); + + //auto effect = new QGraphicsDropShadowEffect; + //auto effect = new ConnectionBlurEffect(this); + //effect->setOffset(4, 4); + //effect->setColor(QColor(Qt::gray).darker(800)); +} diff --git a/NodeEditorPro/src/ConnectionPainter.cpp b/NodeEditorPro/src/ConnectionPainter.cpp new file mode 100644 index 0000000..f162ff0 --- /dev/null +++ b/NodeEditorPro/src/ConnectionPainter.cpp @@ -0,0 +1,313 @@ +#include "ConnectionPainter.hpp" + +#include + +#include "ConnectionGeometry.hpp" +#include "ConnectionState.hpp" +#include "ConnectionGraphicsObject.hpp" +#include "Connection.hpp" + +#include "NodeData.hpp" + +#include "StyleCollection.hpp" + + +using QtNodes::ConnectionPainter; +using QtNodes::ConnectionGeometry; +using QtNodes::Connection; + + +static +QPainterPath +cubicPath(ConnectionGeometry const& geom) +{ + QPointF const& source = geom.source(); + QPointF const& sink = geom.sink(); + + auto c1c2 = geom.pointsC1C2(); + + // cubic spline + QPainterPath cubic(source); + + cubic.cubicTo(c1c2.first, c1c2.second, sink); + + return cubic; +} + + +QPainterPath +ConnectionPainter:: +getPainterStroke(ConnectionGeometry const& geom) +{ + auto cubic = cubicPath(geom); + + QPointF const& source = geom.source(); + QPainterPath result(source); + + unsigned segments = 20; + + for (auto i = 0ul; i < segments; ++i) + { + double ratio = double(i + 1) / segments; + result.lineTo(cubic.pointAtPercent(ratio)); + } + + QPainterPathStroker stroker; stroker.setWidth(10.0); + + return stroker.createStroke(result); +} + + +#ifdef NODE_DEBUG_DRAWING +static +void +debugDrawing(QPainter* painter, + Connection const& connection) +{ + Q_UNUSED(painter); + Q_UNUSED(connection); + ConnectionGeometry const& geom = + connection.connectionGeometry(); + + { + QPointF const& source = geom.source(); + QPointF const& sink = geom.sink(); + + auto points = geom.pointsC1C2(); + + painter->setPen(Qt::red); + painter->setBrush(Qt::red); + + painter->drawLine(QLineF(source, points.first)); + painter->drawLine(QLineF(points.first, points.second)); + painter->drawLine(QLineF(points.second, sink)); + painter->drawEllipse(points.first, 3, 3); + painter->drawEllipse(points.second, 3, 3); + + painter->setBrush(Qt::NoBrush); + + painter->drawPath(cubicPath(geom)); + } + + { + painter->setPen(Qt::yellow); + + painter->drawRect(geom.boundingRect()); + } +} +#endif + +static +void +drawSketchLine(QPainter* painter, + Connection const& connection) +{ + using QtNodes::ConnectionState; + + ConnectionState const& state = + connection.connectionState(); + + if (state.requiresPort()) + { + auto const& connectionStyle = + QtNodes::StyleCollection::connectionStyle(); + + QPen p; + p.setWidth(connectionStyle.constructionLineWidth()); + p.setColor(connectionStyle.constructionColor()); + p.setStyle(Qt::DashLine); + + painter->setPen(p); + painter->setBrush(Qt::NoBrush); + + using QtNodes::ConnectionGeometry; + ConnectionGeometry const& geom = connection.connectionGeometry(); + + auto cubic = cubicPath(geom); + // cubic spline + painter->drawPath(cubic); + } +} + +static +void +drawHoveredOrSelected(QPainter* painter, + Connection const& connection) +{ + using QtNodes::ConnectionGeometry; + + ConnectionGeometry const& geom = connection.connectionGeometry(); + bool const hovered = geom.hovered(); + + auto const& graphicsObject = + connection.getConnectionGraphicsObject(); + + bool const selected = graphicsObject.isSelected(); + + // drawn as a fat background + if (hovered || selected) + { + QPen p; + + auto const& connectionStyle = + QtNodes::StyleCollection::connectionStyle(); + double const lineWidth = connectionStyle.lineWidth(); + + p.setWidth(2 * lineWidth); + p.setColor(selected ? + connectionStyle.selectedHaloColor() : + connectionStyle.hoveredColor()); + + painter->setPen(p); + painter->setBrush(Qt::NoBrush); + + // cubic spline + auto cubic = cubicPath(geom); + painter->drawPath(cubic); + } +} + + +static +void +drawNormalLine(QPainter* painter, + Connection const& connection) +{ + using QtNodes::ConnectionState; + + ConnectionState const& state = + connection.connectionState(); + + if (state.requiresPort()) + return; + + // colors + + auto const& connectionStyle = + QtNodes::StyleCollection::connectionStyle(); + + QColor normalColorOut = connectionStyle.normalColor(); + QColor normalColorIn = connectionStyle.normalColor(); + QColor selectedColor = connectionStyle.selectedColor(); + + bool gradientColor = false; + + if (connectionStyle.useDataDefinedColors()) + { + using QtNodes::PortType; + + auto dataTypeOut = connection.dataType(PortType::Out); + auto dataTypeIn = connection.dataType(PortType::In); + + gradientColor = (dataTypeOut.id != dataTypeIn.id); + + normalColorOut = connectionStyle.normalColor(dataTypeOut.id); + normalColorIn = connectionStyle.normalColor(dataTypeIn.id); + selectedColor = normalColorOut.darker(200); + } + + // geometry + + ConnectionGeometry const& geom = connection.connectionGeometry(); + + double const lineWidth = connectionStyle.lineWidth(); + + // draw normal line + QPen p; + + p.setWidth(lineWidth); + + auto const& graphicsObject = connection.getConnectionGraphicsObject(); + bool const selected = graphicsObject.isSelected(); + + + auto cubic = cubicPath(geom); + if (gradientColor) + { + painter->setBrush(Qt::NoBrush); + + QColor cOut = normalColorOut; + if (selected) + cOut = cOut.darker(200); + p.setColor(cOut); + painter->setPen(p); + + unsigned int const segments = 60; + + for (unsigned int i = 0ul; i < segments; ++i) + { + double ratioPrev = double(i) / segments; + double ratio = double(i + 1) / segments; + + if (i == segments / 2) + { + QColor cIn = normalColorIn; + if (selected) + cIn = cIn.darker(200); + + p.setColor(cIn); + painter->setPen(p); + } + painter->drawLine(cubic.pointAtPercent(ratioPrev), + cubic.pointAtPercent(ratio)); + } + + { + QIcon icon(":convert.png"); + + QPixmap pixmap = icon.pixmap(QSize(22, 22)); + painter->drawPixmap(cubic.pointAtPercent(0.50) - QPoint(pixmap.width() / 2, + pixmap.height() / 2), + pixmap); + + } + } + else + { + p.setColor(normalColorOut); + + if (selected) + { + p.setColor(selectedColor); + } + + painter->setPen(p); + painter->setBrush(Qt::NoBrush); + + painter->drawPath(cubic); + } +} + + +void +ConnectionPainter:: +paint(QPainter* painter, + Connection const& connection) +{ + drawHoveredOrSelected(painter, connection); + + drawSketchLine(painter, connection); + + drawNormalLine(painter, connection); + +#ifdef NODE_DEBUG_DRAWING + debugDrawing(painter, connection); +#endif + + // draw end points + ConnectionGeometry const& geom = connection.connectionGeometry(); + + QPointF const& source = geom.source(); + QPointF const& sink = geom.sink(); + + auto const& connectionStyle = + QtNodes::StyleCollection::connectionStyle(); + + double const pointDiameter = connectionStyle.pointDiameter(); + + painter->setPen(connectionStyle.constructionColor()); + painter->setBrush(connectionStyle.constructionColor()); + double const pointRadius = pointDiameter / 2.0; + painter->drawEllipse(source, pointRadius, pointRadius); + painter->drawEllipse(sink, pointRadius, pointRadius); +} diff --git a/NodeEditorPro/src/ConnectionPainter.hpp b/NodeEditorPro/src/ConnectionPainter.hpp new file mode 100644 index 0000000..11a262b --- /dev/null +++ b/NodeEditorPro/src/ConnectionPainter.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace QtNodes +{ + + class ConnectionGeometry; + class ConnectionState; + class Connection; + + class ConnectionPainter + { + public: + + static + void + paint(QPainter* painter, + Connection const& connection); + + static + QPainterPath + getPainterStroke(ConnectionGeometry const& geom); + }; +} diff --git a/NodeEditorPro/src/ConnectionState.cpp b/NodeEditorPro/src/ConnectionState.cpp new file mode 100644 index 0000000..893dbfe --- /dev/null +++ b/NodeEditorPro/src/ConnectionState.cpp @@ -0,0 +1,51 @@ +#include "ConnectionState.hpp" + +#include + +#include + +#include "FlowScene.hpp" +#include "Node.hpp" + +using QtNodes::ConnectionState; +using QtNodes::Node; + +ConnectionState:: +~ConnectionState() +{ + resetLastHoveredNode(); +} + + +void +ConnectionState:: +interactWithNode(Node* node) +{ + if (node) + { + _lastHoveredNode = node; + } + else + { + resetLastHoveredNode(); + } +} + + +void +ConnectionState:: +setLastHoveredNode(Node* node) +{ + _lastHoveredNode = node; +} + + +void +ConnectionState:: +resetLastHoveredNode() +{ + if (_lastHoveredNode) + _lastHoveredNode->resetReactionToConnection(); + + _lastHoveredNode = nullptr; +} diff --git a/NodeEditorPro/src/ConnectionStyle.cpp b/NodeEditorPro/src/ConnectionStyle.cpp new file mode 100644 index 0000000..adfd7c7 --- /dev/null +++ b/NodeEditorPro/src/ConnectionStyle.cpp @@ -0,0 +1,165 @@ +#include "ConnectionStyle.hpp" + +#include "StyleCollection.hpp" + +#include +#include +#include +#include +#include + +#include + +#include +#include "QJsonParser.hpp" + +using QtNodes::ConnectionStyle; + +inline void initResources() { Q_INIT_RESOURCE(resources); } + +ConnectionStyle:: +ConnectionStyle() : + ConstructionColor(20, 20, 20, 255), + NormalColor(0, 139, 139, 255), + SelectedColor(100, 100, 100, 255), + SelectedHaloColor(255, 165, 0, 255), + HoveredColor(225, 255, 255, 255), + LineWidth(3.0), + ConstructionLineWidth(2.0), + PointDiameter(10.0), + UseDataDefinedColors(false) +{ + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile("DefaultStyle.json"); +} + + +ConnectionStyle:: +ConnectionStyle(QString fileName) +{ + loadJsonFile(fileName); +} + + +void +ConnectionStyle:: +setConnectionStyle(QString jsonText) +{ + ConnectionStyle style(jsonText); + + StyleCollection::setConnectionStyle(style); +} + +void +ConnectionStyle:: +loadJsonFile(QString styleFile) +{ + QJsonObject obj = QJsonConvert::readJsonObj(styleFile); + QJsonObject styleObj = obj["ConnectionStyle"].toObject(); + if (styleObj.isEmpty()) + { + saveJsonFile(styleFile); + } + else + { + QJsonConvert::convertFromJson(obj["ConnectionStyle"].toObject(), *this); + } +} + +void ConnectionStyle::saveJsonFile(QString fileName) +{ + QJsonObject obj = QJsonConvert::readJsonObj(fileName); + obj.insert("ConnectionStyle", QJsonConvert::convertToJson(*this)); + QJsonConvert::writeJsonObj(fileName, obj); +} + +QColor +ConnectionStyle:: +constructionColor() const +{ + return ConstructionColor; +} + + +QColor +ConnectionStyle:: +normalColor() const +{ + return NormalColor; +} + + +QColor +ConnectionStyle:: +normalColor(QString typeId) const +{ + std::size_t hash = qHash(typeId); + + std::size_t const hue_range = 0xFF; + + std::mt19937 gen(static_cast(hash)); + std::uniform_int_distribution distrib(0, hue_range); + + int hue = distrib(gen); + int sat = 120 + hash % 129; + + return QColor::fromHsl(hue, + sat, + 160); +} + + +QColor +ConnectionStyle:: +selectedColor() const +{ + return SelectedColor; +} + + +QColor +ConnectionStyle:: +selectedHaloColor() const +{ + return SelectedHaloColor; +} + + +QColor +ConnectionStyle:: +hoveredColor() const +{ + return HoveredColor; +} + + +float +ConnectionStyle:: +lineWidth() const +{ + return LineWidth; +} + + +float +ConnectionStyle:: +constructionLineWidth() const +{ + return ConstructionLineWidth; +} + + +float +ConnectionStyle:: +pointDiameter() const +{ + return PointDiameter; +} + + +bool +ConnectionStyle:: +useDataDefinedColors() const +{ + return UseDataDefinedColors; +} diff --git a/NodeEditorPro/src/DataModelRegistry.cpp b/NodeEditorPro/src/DataModelRegistry.cpp new file mode 100644 index 0000000..2d873c6 --- /dev/null +++ b/NodeEditorPro/src/DataModelRegistry.cpp @@ -0,0 +1,65 @@ +#include "DataModelRegistry.hpp" + +#include +#include + +using QtNodes::DataModelRegistry; +using QtNodes::NodeDataModel; +using QtNodes::NodeDataType; +using QtNodes::TypeConverter; + +std::unique_ptr +DataModelRegistry:: +create(QString const& modelName) +{ + auto it = _registeredItemCreators.find(modelName); + + if (it != _registeredItemCreators.end()) + { + return it->second(); + } + + return nullptr; +} + + +DataModelRegistry::RegisteredModelCreatorsMap const& +DataModelRegistry:: +registeredModelCreators() const +{ + return _registeredItemCreators; +} + + +DataModelRegistry::RegisteredModelsCategoryMap const& +DataModelRegistry:: +registeredModelsCategoryAssociation() const +{ + return _registeredModelsCategory; +} + + +DataModelRegistry::CategoriesSet const& +DataModelRegistry:: +categories() const +{ + return _categories; +} + + +TypeConverter +DataModelRegistry:: +getTypeConverter(NodeDataType const& d1, + NodeDataType const& d2) const +{ + TypeConverterId converterId = std::make_pair(d1, d2); + + auto it = _registeredTypeConverters.find(converterId); + + if (it != _registeredTypeConverters.end()) + { + return it->second; + } + + return TypeConverter{}; +} diff --git a/NodeEditorPro/src/FlowScene.cpp b/NodeEditorPro/src/FlowScene.cpp new file mode 100644 index 0000000..c400006 --- /dev/null +++ b/NodeEditorPro/src/FlowScene.cpp @@ -0,0 +1,671 @@ +#include "FlowScene.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "Node.hpp" +#include "NodeGraphicsObject.hpp" + +#include "NodeGraphicsObject.hpp" +#include "ConnectionGraphicsObject.hpp" + +#include "Connection.hpp" + +#include "FlowView.hpp" +#include "DataModelRegistry.hpp" + +using QtNodes::FlowScene; +using QtNodes::Node; +using QtNodes::NodeGraphicsObject; +using QtNodes::Connection; +using QtNodes::DataModelRegistry; +using QtNodes::NodeDataModel; +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::TypeConverter; + + +FlowScene:: +FlowScene(std::shared_ptr registry, + QObject* parent) + : QGraphicsScene(parent) + , _registry(registry) +{ + setItemIndexMethod(QGraphicsScene::NoIndex); + + //qRegisterMetaType("QtNodes::Node"); + qRegisterMetaType("PortIndex"); + + // This connection should come first + connect(this, &FlowScene::connectionCreated, this, &FlowScene::setupConnectionSignals); + connect(this, &FlowScene::connectionCreated, this, &FlowScene::sendConnectionCreatedToNodes); + connect(this, &FlowScene::connectionDeleted, this, &FlowScene::sendConnectionDeletedToNodes); +} + +FlowScene:: +FlowScene(QObject* parent) + : FlowScene(std::make_shared(), + parent) +{} + + +FlowScene:: +~FlowScene() +{ + clearScene(); +} + + +//------------------------------------------------------------------------------ + +std::shared_ptr +FlowScene:: +createConnection(PortType connectedPort, + Node& node, + PortIndex portIndex) +{ + auto connection = std::make_shared(connectedPort, node, portIndex); + + auto cgo = detail::make_unique(*this, *connection); + + // after this function connection points are set to node port + connection->setGraphicsObject(std::move(cgo)); + + _connections[connection->id()] = connection; + + // Note: this connection isn't truly created yet. It's only partially created. + // Thus, don't send the connectionCreated(...) signal. + + connect(connection.get(), + &Connection::connectionCompleted, + this, + [this](Connection const& c) { + emit connectionCreated(c); + }); + + return connection; +} + + +std::shared_ptr +FlowScene:: +createConnection(Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter const& converter) +{ + auto connection = + std::make_shared(nodeIn, + portIndexIn, + nodeOut, + portIndexOut, + converter); + + auto cgo = detail::make_unique(*this, *connection); + + nodeIn.nodeState().setConnection(PortType::In, portIndexIn, *connection); + nodeOut.nodeState().setConnection(PortType::Out, portIndexOut, *connection); + + // after this function connection points are set to node port + connection->setGraphicsObject(std::move(cgo)); + + // trigger data propagation + nodeOut.onDataUpdated(portIndexOut); + + _connections[connection->id()] = connection; + + emit connectionCreated(*connection); + + return connection; +} + + +std::shared_ptr +FlowScene:: +restoreConnection(QJsonObject const& connectionJson) +{ + QUuid nodeInId = QUuid(connectionJson["in_id"].toString()); + QUuid nodeOutId = QUuid(connectionJson["out_id"].toString()); + + PortIndex portIndexIn = connectionJson["in_index"].toInt(); + PortIndex portIndexOut = connectionJson["out_index"].toInt(); + + auto nodeIn = _nodes[nodeInId].get(); + auto nodeOut = _nodes[nodeOutId].get(); + + auto getConverter = [&]() + { + QJsonValue converterVal = connectionJson["converter"]; + + if (!converterVal.isUndefined()) + { + QJsonObject converterJson = converterVal.toObject(); + + NodeDataType inType{ converterJson["in"].toObject()["id"].toString(), + converterJson["in"].toObject()["name"].toString() }; + + NodeDataType outType{ converterJson["out"].toObject()["id"].toString(), + converterJson["out"].toObject()["name"].toString() }; + + auto converter = + registry().getTypeConverter(outType, inType); + + if (converter) + return converter; + } + + return TypeConverter{}; + }; + + std::shared_ptr connection = + createConnection(*nodeIn, portIndexIn, + *nodeOut, portIndexOut, + getConverter()); + + // Note: the connectionCreated(...) signal has already been sent + // by createConnection(...) + + return connection; +} + + +void +FlowScene:: +deleteConnection(Connection const& connection) +{ + auto it = _connections.find(connection.id()); + if (it != _connections.end()) + { + connection.removeFromNodes(); + _connections.erase(it); + } +} + + +Node& +FlowScene:: +createNode(std::unique_ptr&& dataModel) +{ + auto node = detail::make_unique(std::move(dataModel)); + //QThread* childThread = new QThread; + //node->moveToThread(childThread); + //childThread->start(); + //qDebug() << "createNode childThread: " << childThread->currentThreadId(); + //connect(childThread, &QThread::finished, this, &FlowScene::clearScene); + + auto ngo = detail::make_unique(*this, *node); + + node->setGraphicsObject(std::move(ngo)); + + auto nodePtr = node.get(); + _nodes[node->id()] = std::move(node); + + emit nodeCreated(*nodePtr); + return *nodePtr; +} + + +Node& +FlowScene:: +restoreNode(QJsonObject const& nodeJson) +{ + QString modelName = nodeJson["model"].toObject()["name"].toString(); + + auto dataModel = registry().create(modelName); + + if (!dataModel) + throw std::logic_error(std::string("No registered model with name ") + + modelName.toLocal8Bit().data()); + + auto node = detail::make_unique(std::move(dataModel)); + auto ngo = detail::make_unique(*this, *node); + node->setGraphicsObject(std::move(ngo)); + + node->restore(nodeJson); + + auto nodePtr = node.get(); + _nodes[node->id()] = std::move(node); + + emit nodePlaced(*nodePtr); + emit nodeCreated(*nodePtr); + return *nodePtr; +} + + +void +FlowScene:: +removeNode(Node& node) +{ + // call signal + emit nodeDeleted(node); + + for (auto portType : { PortType::In,PortType::Out }) + { + auto nodeState = node.nodeState(); + auto const& nodeEntries = nodeState.getEntries(portType); + + for (auto& connections : nodeEntries) + { + for (auto const& pair : connections) + deleteConnection(*pair.second); + } + } + + _nodes.erase(node.id()); +} + + +DataModelRegistry& +FlowScene:: +registry() const +{ + return *_registry; +} + + +void +FlowScene:: +setRegistry(std::shared_ptr registry) +{ + _registry = std::move(registry); +} + + +void +FlowScene:: +iterateOverNodes(std::function const& visitor) +{ + for (const auto& _node : _nodes) + { + visitor(_node.second.get()); + } +} + + +void +FlowScene:: +iterateOverNodeData(std::function const& visitor) +{ + for (const auto& _node : _nodes) + { + visitor(_node.second->nodeDataModel()); + } +} + + +void +FlowScene:: +iterateOverNodeDataDependentOrder(std::function const& visitor) +{ + std::set visitedNodesSet; + + //A leaf node is a node with no input ports, or all possible input ports empty + auto isNodeLeaf = + [](Node const& node, NodeDataModel const& model) + { + for (unsigned int i = 0; i < model.nPorts(PortType::In); ++i) + { + auto connections = node.nodeState().connections(PortType::In, i); + if (!connections.empty()) + { + return false; + } + } + + return true; + }; + + //Iterate over "leaf" nodes + for (auto const& _node : _nodes) + { + auto const& node = _node.second; + auto model = node->nodeDataModel(); + + if (isNodeLeaf(*node, *model)) + { + visitor(model); + visitedNodesSet.insert(node->id()); + } + } + + auto areNodeInputsVisitedBefore = + [&](Node const& node, NodeDataModel const& model) + { + for (size_t i = 0; i < model.nPorts(PortType::In); ++i) + { + auto connections = node.nodeState().connections(PortType::In, static_cast(i)); + + for (auto& conn : connections) + { + if (visitedNodesSet.find(conn.second->getNode(PortType::Out)->id()) == visitedNodesSet.end()) + { + return false; + } + } + } + + return true; + }; + + //Iterate over dependent nodes + while (_nodes.size() != visitedNodesSet.size()) + { + for (auto const& _node : _nodes) + { + auto const& node = _node.second; + if (visitedNodesSet.find(node->id()) != visitedNodesSet.end()) + continue; + + auto model = node->nodeDataModel(); + + if (areNodeInputsVisitedBefore(*node, *model)) + { + visitor(model); + visitedNodesSet.insert(node->id()); + } + } + } +} + + +QPointF +FlowScene:: +getNodePosition(const Node& node) const +{ + return node.nodeGraphicsObject().pos(); +} + + +void +FlowScene:: +setNodePosition(Node& node, const QPointF& pos) const +{ + node.nodeGraphicsObject().setPos(pos); + node.nodeGraphicsObject().moveConnections(); +} + + +QSizeF +FlowScene:: +getNodeSize(const Node& node) const +{ + return QSizeF(node.nodeGeometry().width(), node.nodeGeometry().height()); +} + + +std::unordered_map > const& +FlowScene:: +nodes() const +{ + return _nodes; +} + + +std::unordered_map > const& +FlowScene:: +connections() const +{ + return _connections; +} + + +std::vector +FlowScene:: +allNodes() const +{ + std::vector nodes; + + std::transform(_nodes.begin(), + _nodes.end(), + std::back_inserter(nodes), + [](std::pair> const& p) { return p.second.get(); }); + + return nodes; +} + + +std::vector +FlowScene:: +selectedNodes() const +{ + QList graphicsItems = selectedItems(); + + std::vector ret; + ret.reserve(graphicsItems.size()); + + for (QGraphicsItem* item : graphicsItems) + { + auto ngo = qgraphicsitem_cast(item); + + if (ngo != nullptr) + { + ret.push_back(&ngo->node()); + } + } + + return ret; +} + + +//------------------------------------------------------------------------------ + +void +FlowScene:: +clearScene() +{ + //Manual node cleanup. Simply clearing the holding datastructures doesn't work, the code crashes when + // there are both nodes and connections in the scene. (The data propagation internal logic tries to propagate + // data through already freed connections.) + while (_connections.size() > 0) + { + deleteConnection(*_connections.begin()->second); + } + while (_nodes.size() > 0) + { + removeNode(*_nodes.begin()->second); + } +} + +void +FlowScene:: +save() const +{ + QString fileName = + QFileDialog::getSaveFileName(nullptr, + tr("Open Flow Scene"), + QDir::homePath(), + tr("Flow Scene Files (*.flow)")); + + if (!fileName.isEmpty()) + { + if (!fileName.endsWith("flow", Qt::CaseInsensitive)) + fileName += ".flow"; + + QFile file(fileName); + if (file.open(QIODevice::WriteOnly)) + { + file.write(saveToMemory()); + } + } +} + + +void +FlowScene:: +load() +{ + QString fileName = + QFileDialog::getOpenFileName(nullptr, + tr("Open Flow Scene"), + QDir::homePath(), + tr("Flow Scene Files (*.flow)")); + + if (!QFileInfo::exists(fileName)) + return; + + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) + return; + + clearScene(); + + QByteArray wholeFile = file.readAll(); + + loadFromMemory(wholeFile); +} + + +QByteArray +FlowScene:: +saveToMemory() const +{ + QJsonObject sceneJson; + + QJsonArray nodesJsonArray; + + for (auto const& pair : _nodes) + { + auto const& node = pair.second; + + nodesJsonArray.append(node->save()); + } + + sceneJson["nodes"] = nodesJsonArray; + + QJsonArray connectionJsonArray; + for (auto const& pair : _connections) + { + auto const& connection = pair.second; + + QJsonObject connectionJson = connection->save(); + + if (!connectionJson.isEmpty()) + connectionJsonArray.append(connectionJson); + } + + sceneJson["connections"] = connectionJsonArray; + + QJsonDocument document(sceneJson); + + return document.toJson(); +} + + +void +FlowScene:: +loadFromMemory(const QByteArray& data) +{ + QJsonObject const jsonDocument = QJsonDocument::fromJson(data).object(); + + QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); + + for (QJsonValueRef node : nodesJsonArray) + { + restoreNode(node.toObject()); + } + + QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); + + for (QJsonValueRef connection : connectionJsonArray) + { + restoreConnection(connection.toObject()); + } +} + + +void +FlowScene:: +setupConnectionSignals(Connection const& c) +{ + connect(&c, + &Connection::connectionMadeIncomplete, + this, + &FlowScene::connectionDeleted, + Qt::UniqueConnection); +} + + +void +FlowScene:: +sendConnectionCreatedToNodes(Connection const& c) +{ + Node* from = c.getNode(PortType::Out); + Node* to = c.getNode(PortType::In); + + Q_ASSERT(from != nullptr); + Q_ASSERT(to != nullptr); + + from->nodeDataModel()->outputConnectionCreated(c); + to->nodeDataModel()->inputConnectionCreated(c); +} + + +void +FlowScene:: +sendConnectionDeletedToNodes(Connection const& c) +{ + Node* from = c.getNode(PortType::Out); + Node* to = c.getNode(PortType::In); + + Q_ASSERT(from != nullptr); + Q_ASSERT(to != nullptr); + + from->nodeDataModel()->outputConnectionDeleted(c); + to->nodeDataModel()->inputConnectionDeleted(c); +} + + +//------------------------------------------------------------------------------ +namespace QtNodes +{ + + Node* + locateNodeAt(QPointF scenePoint, FlowScene& scene, + QTransform const& viewTransform) + { + // items under cursor + QList items = + scene.items(scenePoint, + Qt::IntersectsItemShape, + Qt::DescendingOrder, + viewTransform); + + //// items convertable to NodeGraphicsObject + std::vector filteredItems; + + std::copy_if(items.begin(), + items.end(), + std::back_inserter(filteredItems), + [](QGraphicsItem* item) + { + return (dynamic_cast(item) != nullptr); + }); + + Node* resultNode = nullptr; + + if (!filteredItems.empty()) + { + QGraphicsItem* graphicsItem = filteredItems.front(); + auto ngo = dynamic_cast(graphicsItem); + + resultNode = &ngo->node(); + } + + return resultNode; + } +} diff --git a/NodeEditorPro/src/FlowView.cpp b/NodeEditorPro/src/FlowView.cpp new file mode 100644 index 0000000..9192b42 --- /dev/null +++ b/NodeEditorPro/src/FlowView.cpp @@ -0,0 +1,417 @@ +#include "FlowView.hpp" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include "FlowScene.hpp" +#include "DataModelRegistry.hpp" +#include "Node.hpp" +#include "NodeGraphicsObject.hpp" +#include "ConnectionGraphicsObject.hpp" +#include "StyleCollection.hpp" + +using QtNodes::FlowView; +using QtNodes::FlowScene; + +FlowView::FlowView(QWidget* parent) + : QGraphicsView(parent) + , _clearSelectionAction(Q_NULLPTR) + , _deleteSelectionAction(Q_NULLPTR) + , _scene(Q_NULLPTR) +{ + setDragMode(QGraphicsView::ScrollHandDrag); + setRenderHint(QPainter::Antialiasing); + + auto const& flowViewStyle = StyleCollection::flowViewStyle(); + + setBackgroundBrush(flowViewStyle.BackgroundColor); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + + setCacheMode(QGraphicsView::CacheBackground); + setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + + setDragMode(DragMode::NoDrag); + //setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); +} + + +FlowView:: +FlowView(FlowScene* scene, QWidget* parent) + : FlowView(parent) +{ + setScene(scene); +} + + +QAction* +FlowView:: +clearSelectionAction() const +{ + return _clearSelectionAction; +} + + +QAction* +FlowView:: +deleteSelectionAction() const +{ + return _deleteSelectionAction; +} + + +void +FlowView::setScene(FlowScene* scene) +{ + _scene = scene; + QGraphicsView::setScene(_scene); + + // setup actions + delete _clearSelectionAction; + _clearSelectionAction = new QAction(QStringLiteral("Clear Selection"), this); + _clearSelectionAction->setShortcut(Qt::Key_Escape); + connect(_clearSelectionAction, &QAction::triggered, _scene, &QGraphicsScene::clearSelection); + addAction(_clearSelectionAction); + + delete _deleteSelectionAction; + _deleteSelectionAction = new QAction(QStringLiteral("Delete Selection"), this); + _deleteSelectionAction->setShortcut(Qt::Key_Delete); + connect(_deleteSelectionAction, &QAction::triggered, this, &FlowView::deleteSelectedNodes); + addAction(_deleteSelectionAction); +} + + +void +FlowView:: +contextMenuEvent(QContextMenuEvent* event) +{ + if (itemAt(event->pos())) + { + QGraphicsView::contextMenuEvent(event); + return; + } + + QMenu modelMenu; + + auto skipText = QStringLiteral("skip me"); + + //添加一个筛选lineEdit + auto* txtBox = new QLineEdit(&modelMenu); + + txtBox->setPlaceholderText(QStringLiteral("Filter")); + txtBox->setClearButtonEnabled(true); + + auto* txtBoxAction = new QWidgetAction(&modelMenu); + txtBoxAction->setDefaultWidget(txtBox); + + modelMenu.addAction(txtBoxAction); + + //Add result treeview to the context menu + auto* treeView = new QTreeWidget(&modelMenu); + treeView->header()->close(); + + auto* treeViewAction = new QWidgetAction(&modelMenu); + treeViewAction->setDefaultWidget(treeView); + + modelMenu.addAction(treeViewAction); + + QMap topLevelItems; + for (auto const& cat : _scene->registry().categories()) + { + auto item = new QTreeWidgetItem(treeView); + item->setText(0, cat); + item->setData(0, Qt::UserRole, skipText); + topLevelItems[cat] = item; + } + + for (auto const& assoc : _scene->registry().registeredModelsCategoryAssociation()) + { + auto parent = topLevelItems[assoc.second]; + auto item = new QTreeWidgetItem(parent); + item->setText(0, assoc.first); + item->setData(0, Qt::UserRole, assoc.first); + } + + treeView->expandAll(); + + connect(treeView, &QTreeWidget::itemClicked, [&](QTreeWidgetItem* item, int) + { + QString modelName = item->data(0, Qt::UserRole).toString(); + + if (modelName == skipText) + { + return; + } + + auto type = _scene->registry().create(modelName); + + if (type) + { + auto& node = _scene->createNode(std::move(type)); + + QPoint pos = event->pos(); + + QPointF posView = this->mapToScene(pos); + + node.nodeGraphicsObject().setPos(posView); + + _scene->nodePlaced(node); + } + else + { + qDebug() << "Model not found"; + } + + modelMenu.close(); + }); + + //Setup filtering + connect(txtBox, &QLineEdit::textChanged, [&](const QString& text) + { + for (auto& topLvlItem : topLevelItems) + { + for (int i = 0; i < topLvlItem->childCount(); ++i) + { + auto child = topLvlItem->child(i); + auto modelName = child->data(0, Qt::UserRole).toString(); + const bool match = (modelName.contains(text, Qt::CaseInsensitive)); + child->setHidden(!match); + } + } + }); + + // make sure the text box gets focus so the user doesn't have to click on it + txtBox->setFocus(); + treeView->setFixedHeight(380); + modelMenu.setFixedSize(320, 410); + modelMenu.exec(event->globalPos()); + +} + + +void +FlowView:: +wheelEvent(QWheelEvent* event) +{ + //qDebug() << event->type(); + QPoint delta = event->angleDelta(); + if (delta.y() == 0) + { + event->ignore(); + return; + } + double const d = delta.y() / std::abs(delta.y()); + if (d > 0.0) + scaleUp(); + else + scaleDown(); +} + + +void +FlowView:: +scaleUp() +{ + double const step = 1.2; + double const factor = std::pow(step, 1.0); + + QTransform t = transform(); + + if (t.m11() > 2.0) + return; + + scale(factor, factor); +} + + +void +FlowView:: +scaleDown() +{ + double const step = 1.2; + double const factor = std::pow(step, -1.0); + + scale(factor, factor); +} + + +void +FlowView:: +deleteSelectedNodes() +{ + // Delete the selected connections first, ensuring that they won't be + // automatically deleted when selected nodes are deleted (deleting a node + // deletes some connections as well) + for (QGraphicsItem* item : _scene->selectedItems()) + { + if (auto c = qgraphicsitem_cast(item)) + _scene->deleteConnection(c->connection()); + } + + // Delete the nodes; this will delete many of the connections. + // Selected connections were already deleted prior to this loop, otherwise + // qgraphicsitem_cast(item) could be a use-after-free + // when a selected connection is deleted by deleting the node. + for (QGraphicsItem* item : _scene->selectedItems()) + { + if (auto n = qgraphicsitem_cast(item)) + _scene->removeNode(n->node()); + } +} + + +void +FlowView:: +keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) + { + case Qt::Key_Shift: + setDragMode(QGraphicsView::RubberBandDrag); + break; + + default: + break; + } + + QGraphicsView::keyPressEvent(event); +} + + +void +FlowView:: +keyReleaseEvent(QKeyEvent* event) +{ + switch (event->key()) + { + case Qt::Key_Shift: + setDragMode(QGraphicsView::ScrollHandDrag); + break; + + default: + break; + } + QGraphicsView::keyReleaseEvent(event); +} + + +void +FlowView:: +mousePressEvent(QMouseEvent* event) +{ + QGraphicsView::mousePressEvent(event); + if (event->button() == Qt::LeftButton) + { + this->setDragMode(DragMode::RubberBandDrag); + _clickPos = mapToScene(event->pos()); + } +} + + +void +FlowView:: +mouseMoveEvent(QMouseEvent* event) +{ + QGraphicsView::mouseMoveEvent(event); + if (scene()->mouseGrabberItem() == nullptr && event->buttons() == Qt::LeftButton) + { + this->setDragMode(DragMode::RubberBandDrag); + // Make sure shift is not being pressed + if ((event->modifiers() & Qt::ShiftModifier) == 0) + { + QPointF difference = _clickPos - mapToScene(event->pos()); + setSceneRect(sceneRect().translated(difference.x(), difference.y())); + } + } +} + +void FlowView::mouseReleaseEvent(QMouseEvent* event) +{ + this->setDragMode(DragMode::NoDrag); + QGraphicsView::mouseReleaseEvent(event); +} + + +void +FlowView:: +drawBackground(QPainter* painter, const QRectF& r) +{ + QGraphicsView::drawBackground(painter, r); + + auto drawGrid = + [&](double gridStep) + { + QRect windowRect = rect(); + QPointF tl = mapToScene(windowRect.topLeft()); + QPointF br = mapToScene(windowRect.bottomRight()); + + double left = std::floor(tl.x() / gridStep - 0.5); + double right = std::floor(br.x() / gridStep + 1.0); + double bottom = std::floor(tl.y() / gridStep - 0.5); + double top = std::floor(br.y() / gridStep + 1.0); + + // vertical lines + for (int xi = int(left); xi <= int(right); ++xi) + { + QLineF line(xi * gridStep, bottom * gridStep, + xi * gridStep, top * gridStep); + + painter->drawLine(line); + } + + // horizontal lines + for (int yi = int(bottom); yi <= int(top); ++yi) + { + QLineF line(left * gridStep, yi * gridStep, + right * gridStep, yi * gridStep); + painter->drawLine(line); + } + }; + + auto const& flowViewStyle = StyleCollection::flowViewStyle(); + + QBrush bBrush = backgroundBrush(); + + QPen pfine(flowViewStyle.FineGridColor, 1.0); + + painter->setPen(pfine); + drawGrid(15); + + QPen p(flowViewStyle.CoarseGridColor, 1.0); + + painter->setPen(p); + drawGrid(150); +} + + +void +FlowView:: +showEvent(QShowEvent* event) +{ + _scene->setSceneRect(this->rect()); + QGraphicsView::showEvent(event); +} + + +FlowScene* +FlowView:: +scene() +{ + return _scene; +} diff --git a/NodeEditorPro/src/FlowViewStyle.cpp b/NodeEditorPro/src/FlowViewStyle.cpp new file mode 100644 index 0000000..c8ccd0c --- /dev/null +++ b/NodeEditorPro/src/FlowViewStyle.cpp @@ -0,0 +1,66 @@ +#include "FlowViewStyle.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "StyleCollection.hpp" +#include "QJsonParser.hpp" + +using QtNodes::FlowViewStyle; + +inline void initResources() { Q_INIT_RESOURCE(resources); } + +FlowViewStyle:: +FlowViewStyle() : + BackgroundColor(53, 53, 53, 255), + FineGridColor(60, 60, 60, 255), + CoarseGridColor(25, 25, 25, 255) +{ + loadJsonFile("DefaultStyle.json"); +} + + +FlowViewStyle:: +FlowViewStyle(QString jsonText) +{ + loadJsonFile(jsonText); +} + + +void +FlowViewStyle:: +setStyle(QString jsonText) +{ + FlowViewStyle style(jsonText); + + StyleCollection::setFlowViewStyle(style); +} + +void +FlowViewStyle:: +loadJsonFile(QString styleFile) +{ + QJsonObject obj = QJsonConvert::readJsonObj(styleFile); + QJsonObject styleObj = obj["FlowViewStyle"].toObject(); + if (styleObj.isEmpty()) + { + saveJsonFile(styleFile); + } + else + { + QJsonConvert::convertFromJson(obj["FlowViewStyle"].toObject(), *this); + } +} + +void QtNodes::FlowViewStyle::saveJsonFile(QString fileName) +{ + QJsonObject obj = QJsonConvert::readJsonObj(fileName); + obj.insert("FlowViewStyle", QJsonConvert::convertToJson(*this)); + QJsonConvert::writeJsonObj(fileName, obj); +} + diff --git a/NodeEditorPro/src/Node.cpp b/NodeEditorPro/src/Node.cpp new file mode 100644 index 0000000..7eda913 --- /dev/null +++ b/NodeEditorPro/src/Node.cpp @@ -0,0 +1,254 @@ +#include "Node.hpp" + +#include + +#include +#include + +#include "FlowScene.hpp" + +#include "NodeGraphicsObject.hpp" +#include "NodeDataModel.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionState.hpp" + +using QtNodes::Node; +using QtNodes::NodeGeometry; +using QtNodes::NodeState; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::NodeGraphicsObject; +using QtNodes::PortIndex; +using QtNodes::PortType; + +Node:: +Node(std::unique_ptr&& dataModel) + : _uid(QUuid::createUuid()) + , _nodeDataModel(std::move(dataModel)) + , _nodeState(_nodeDataModel) + , _nodeGeometry(_nodeDataModel) + , _nodeGraphicsObject(nullptr) +{ + _nodeGeometry.recalculateSize(); + + // propagate data: model => node + connect(_nodeDataModel.get(), &NodeDataModel::dataUpdated, + this, &Node::onDataUpdated); + + connect(_nodeDataModel.get(), &NodeDataModel::dataInvalidated, + this, &Node::onDataInvalidated); + + connect(_nodeDataModel.get(), &NodeDataModel::embeddedWidgetSizeUpdated, + this, &Node::onNodeSizeUpdated); +} + + +Node:: +~Node() = default; + +QJsonObject +Node:: +save() const +{ + QJsonObject nodeJson; + + nodeJson["id"] = _uid.toString(); + + nodeJson["model"] = _nodeDataModel->save(); + + QJsonObject obj; + obj["x"] = _nodeGraphicsObject->pos().x(); + obj["y"] = _nodeGraphicsObject->pos().y(); + nodeJson["position"] = obj; + + return nodeJson; +} + + +void +Node:: +restore(QJsonObject const& json) +{ + _uid = QUuid(json["id"].toString()); + + QJsonObject positionJson = json["position"].toObject(); + QPointF point(positionJson["x"].toDouble(), + positionJson["y"].toDouble()); + _nodeGraphicsObject->setPos(point); + + _nodeDataModel->restore(json["model"].toObject()); +} + + +QUuid +Node:: +id() const +{ + return _uid; +} + + +void +Node:: +reactToPossibleConnection(PortType reactingPortType, + NodeDataType const& reactingDataType, + QPointF const& scenePoint) +{ + QTransform const t = _nodeGraphicsObject->sceneTransform(); + + QPointF p = t.inverted().map(scenePoint); + + _nodeGeometry.setDraggingPosition(p); + + _nodeGraphicsObject->update(); + + _nodeState.setReaction(NodeState::REACTING, + reactingPortType, + reactingDataType); +} + + +void +Node:: +resetReactionToConnection() +{ + _nodeState.setReaction(NodeState::NOT_REACTING); + _nodeGraphicsObject->update(); +} + + +NodeGraphicsObject const& +Node:: +nodeGraphicsObject() const +{ + return *_nodeGraphicsObject.get(); +} + + +NodeGraphicsObject& +Node:: +nodeGraphicsObject() +{ + return *_nodeGraphicsObject.get(); +} + + +void +Node:: +setGraphicsObject(std::unique_ptr&& graphics) +{ + _nodeGraphicsObject = std::move(graphics); + + _nodeGeometry.recalculateSize(); +} + + +NodeGeometry& +Node:: +nodeGeometry() +{ + return _nodeGeometry; +} + + +NodeGeometry const& +Node:: +nodeGeometry() const +{ + return _nodeGeometry; +} + + +NodeState const& +Node:: +nodeState() const +{ + return _nodeState; +} + + +NodeState& +Node:: +nodeState() +{ + return _nodeState; +} + + +NodeDataModel* +Node:: +nodeDataModel() const +{ + return _nodeDataModel.get(); +} + + +void +Node:: +propagateData(std::shared_ptr nodeData, + PortIndex inPortIndex, + const QUuid& connectionId) const +{ + _nodeDataModel->setInData(std::move(nodeData), inPortIndex, connectionId); + + // Recalculate the nodes visuals. A data change can result in the + // node taking more space than before, so this forces a + // recalculate+repaint on the affected node. + _nodeGraphicsObject->setGeometryChanged(); + _nodeGeometry.recalculateSize(); + _nodeGraphicsObject->update(); + _nodeGraphicsObject->moveConnections(); +} + + +void +Node:: +onDataUpdated(PortIndex index) +{ + //QThread thread; + //qDebug() << thread.currentThreadId(); + + auto nodeData = _nodeDataModel->outData(index); + + auto const& connections = + _nodeState.connections(PortType::Out, index); + + for (auto const& c : connections) + c.second->propagateData(nodeData); +} + + +void +Node:: +onDataInvalidated(PortIndex index) +{ + auto const& connections = + _nodeState.connections(PortType::Out, index); + + for (auto const& c : connections) + c.second->propagateEmptyData(); +} + +void +Node:: +onNodeSizeUpdated() +{ + if (nodeDataModel()->embeddedWidget()) + { + nodeDataModel()->embeddedWidget()->adjustSize(); + } + nodeGeometry().recalculateSize(); + for (PortType type : {PortType::In, PortType::Out}) + { + for (auto& conn_set : nodeState().getEntries(type)) + { + for (auto& pair : conn_set) + { + Connection* conn = pair.second; + conn->getConnectionGraphicsObject().move(); + } + } + } +} diff --git a/NodeEditorPro/src/NodeConnectionInteraction.cpp b/NodeEditorPro/src/NodeConnectionInteraction.cpp new file mode 100644 index 0000000..ba1890e --- /dev/null +++ b/NodeEditorPro/src/NodeConnectionInteraction.cpp @@ -0,0 +1,266 @@ +#include "NodeConnectionInteraction.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "NodeGraphicsObject.hpp" +#include "NodeDataModel.hpp" +#include "DataModelRegistry.hpp" +#include "FlowScene.hpp" + +using QtNodes::NodeConnectionInteraction; +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::FlowScene; +using QtNodes::Node; +using QtNodes::Connection; +using QtNodes::NodeDataModel; +using QtNodes::TypeConverter; + + +NodeConnectionInteraction:: +NodeConnectionInteraction(Node& node, Connection& connection, FlowScene& scene) + : _node(&node) + , _connection(&connection) + , _scene(&scene) +{} + + +bool +NodeConnectionInteraction:: +canConnect(PortIndex& portIndex, TypeConverter& converter) const +{ + // 1) Connection requires a port + + PortType requiredPort = connectionRequiredPort(); + + + if (requiredPort == PortType::None) + { + return false; + } + + // 1.5) Forbid connecting the node to itself + Node* node = _connection->getNode(oppositePort(requiredPort)); + + if (node == _node) + return false; + + // 2) connection point is on top of the node port + + QPointF connectionPoint = connectionEndScenePosition(requiredPort); + + portIndex = nodePortIndexUnderScenePoint(requiredPort, + connectionPoint); + + if (portIndex == INVALID) + { + return false; + } + + // 3) Node port is vacant + + // port should be empty + if (!nodePortIsEmpty(requiredPort, portIndex)) + return false; + + // 4) Connection type equals node port type, or there is a registered type conversion that can translate between the two + + auto connectionDataType = + _connection->dataType(oppositePort(requiredPort)); + + auto const& modelTarget = _node->nodeDataModel(); + NodeDataType candidateNodeDataType = modelTarget->dataType(requiredPort, portIndex); + + if (connectionDataType.id != candidateNodeDataType.id) + { + if (requiredPort == PortType::In) + { + converter = _scene->registry().getTypeConverter(connectionDataType, candidateNodeDataType); + } + else if (requiredPort == PortType::Out) + { + converter = _scene->registry().getTypeConverter(candidateNodeDataType, connectionDataType); + } + + return (converter != nullptr); + } + + return true; +} + + +bool +NodeConnectionInteraction:: +tryConnect() const +{ + // 1) Check conditions from 'canConnect' + PortIndex portIndex = INVALID; + + TypeConverter converter; + + if (!canConnect(portIndex, converter)) + { + return false; + } + + // 1.5) If the connection is possible but a type conversion is needed, + // assign a convertor to connection + if (converter) + { + _connection->setTypeConverter(converter); + } + + // 2) Assign node to required port in Connection + PortType requiredPort = connectionRequiredPort(); + _node->nodeState().setConnection(requiredPort, + portIndex, + *_connection); + + // 3) Assign Connection to empty port in NodeState + // The port is not longer required after this function + _connection->setNodeToPort(*_node, requiredPort, portIndex); + + // 4) Adjust Connection geometry + + _node->nodeGraphicsObject().moveConnections(); + + // 5) Poke model to intiate data transfer + + auto outNode = _connection->getNode(PortType::Out); + if (outNode) + { + PortIndex outPortIndex = _connection->getPortIndex(PortType::Out); + outNode->onDataUpdated(outPortIndex); + } + + return true; +} + + +/// 1) Node and Connection should be already connected +/// 2) If so, clear Connection entry in the NodeState +/// 3) Set Connection end to 'requiring a port' +bool +NodeConnectionInteraction:: +disconnect(PortType portToDisconnect) const +{ + PortIndex portIndex = + _connection->getPortIndex(portToDisconnect); + + NodeState& state = _node->nodeState(); + + // clear pointer to Connection in the NodeState + state.getEntries(portToDisconnect)[portIndex].erase(_connection->id()); + + // 4) Propagate invalid data to IN node + _connection->propagateEmptyData(); + + // clear Connection side + _connection->clearNode(portToDisconnect); + + _connection->setRequiredPort(portToDisconnect); + + _connection->getConnectionGraphicsObject().grabMouse(); + + return true; +} + + +// ------------------ util functions below + +PortType +NodeConnectionInteraction:: +connectionRequiredPort() const +{ + auto const& state = _connection->connectionState(); + + return state.requiredPort(); +} + + +QPointF +NodeConnectionInteraction:: +connectionEndScenePosition(PortType portType) const +{ + auto& go = + _connection->getConnectionGraphicsObject(); + + ConnectionGeometry& geometry = _connection->connectionGeometry(); + + QPointF endPoint = geometry.getEndPoint(portType); + + return go.mapToScene(endPoint); +} + + +QPointF +NodeConnectionInteraction:: +nodePortScenePosition(PortType portType, PortIndex portIndex) const +{ + NodeGeometry const& geom = _node->nodeGeometry(); + + QPointF p = geom.portScenePosition(portIndex, portType); + + NodeGraphicsObject& ngo = _node->nodeGraphicsObject(); + + return ngo.sceneTransform().map(p); +} + + +PortIndex +NodeConnectionInteraction:: +nodePortIndexUnderScenePoint(PortType portType, + QPointF const& scenePoint) const +{ + NodeGeometry const& nodeGeom = _node->nodeGeometry(); + + QTransform sceneTransform = + _node->nodeGraphicsObject().sceneTransform(); + + PortIndex portIndex = nodeGeom.checkHitScenePoint(portType, + scenePoint, + sceneTransform); + return portIndex; +} + + +bool +NodeConnectionInteraction:: +nodePortIsEmpty(PortType portType, PortIndex portIndex) const +{ + NodeState const& nodeState = _node->nodeState(); + + auto const& entries = nodeState.getEntries(portType); + auto const& connections = entries[portIndex]; + if (connections.empty()) return true; + + // Check if the connection already exists connected to the respective + // input and output ports + auto sourcePortType = oppositePort(portType); + auto it = std::find_if(connections.begin(), connections.end(), + [this, sourcePortType](const auto& connection) + { + const auto* const currentConn = connection.second; + + assert(_connection->getNode(sourcePortType)); + assert(currentConn->getNode(sourcePortType)); + return _connection->getNode(sourcePortType) == currentConn->getNode(sourcePortType) && + _connection->getPortIndex(sourcePortType) == currentConn->getPortIndex(sourcePortType); + }); + if (it != connections.end()) + return false; + + switch (portType) + { + case PortType::In: + { + const auto policy = _node->nodeDataModel()->portInConnectionPolicy(portIndex); + return policy == NodeDataModel::ConnectionPolicy::Many; + } + case PortType::Out: + { + const auto policy = _node->nodeDataModel()->portOutConnectionPolicy(portIndex); + return policy == NodeDataModel::ConnectionPolicy::Many; + } + default: return false; + } +} diff --git a/NodeEditorPro/src/NodeConnectionInteraction.hpp b/NodeEditorPro/src/NodeConnectionInteraction.hpp new file mode 100644 index 0000000..27dc8ff --- /dev/null +++ b/NodeEditorPro/src/NodeConnectionInteraction.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "Node.hpp" +#include "Connection.hpp" + +namespace QtNodes +{ + + class DataModelRegistry; + class FlowScene; + class NodeDataModel; + + /// Class performs various operations on the Node and Connection pair. + /// An instance should be created on the stack and destroyed when + /// the operation is completed + class NodeConnectionInteraction + { + public: + NodeConnectionInteraction(Node& node, + Connection& connection, + FlowScene& scene); + + /// Can connect when following conditions are met: + /// 1) Connection 'requires' a port + /// 2) Connection's vacant end is above the node port + /// 3) Node port is vacant + /// 4) Connection type equals node port type, or there is a registered type conversion that can translate between the two + bool canConnect(PortIndex& portIndex, + TypeConverter& converter) const; + + /// 1) Check conditions from 'canConnect' + /// 1.5) If the connection is possible but a type conversion is needed, add a converter node to the scene, and connect it properly + /// 2) Assign node to required port in Connection + /// 3) Assign Connection to empty port in NodeState + /// 4) Adjust Connection geometry + /// 5) Poke model to initiate data transfer + bool tryConnect() const; + + + /// 1) Node and Connection should be already connected + /// 2) If so, clear Connection entry in the NodeState + /// 3) Propagate invalid data to IN node + /// 4) Set Connection end to 'requiring a port' + bool disconnect(PortType portToDisconnect) const; + + private: + + PortType connectionRequiredPort() const; + + QPointF connectionEndScenePosition(PortType) const; + + QPointF nodePortScenePosition(PortType portType, + PortIndex portIndex) const; + + PortIndex nodePortIndexUnderScenePoint(PortType portType, + QPointF const& p) const; + + bool nodePortIsEmpty(PortType portType, PortIndex portIndex) const; + + private: + + Node* _node; + + Connection* _connection; + + FlowScene* _scene; + }; +} diff --git a/NodeEditorPro/src/NodeDataModel.cpp b/NodeEditorPro/src/NodeDataModel.cpp new file mode 100644 index 0000000..a3aaebc --- /dev/null +++ b/NodeEditorPro/src/NodeDataModel.cpp @@ -0,0 +1,43 @@ +#include "NodeDataModel.hpp" + +#include "StyleCollection.hpp" + +using QtNodes::NodeDataModel; +using QtNodes::NodeStyle; + +NodeDataModel:: +NodeDataModel() + : _nodeStyle(StyleCollection::nodeStyle()) +{ + // Derived classes can initialize specific style here + //moveToThread(this); + +} + + +QJsonObject +NodeDataModel:: +save() const +{ + QJsonObject modelJson; + + modelJson["name"] = name(); + + return modelJson; +} + + +NodeStyle const& +NodeDataModel:: +nodeStyle() const +{ + return _nodeStyle; +} + + +void +NodeDataModel:: +setNodeStyle(NodeStyle const& style) +{ + _nodeStyle = style; +} diff --git a/NodeEditorPro/src/NodeGeometry.cpp b/NodeEditorPro/src/NodeGeometry.cpp new file mode 100644 index 0000000..ee0f071 --- /dev/null +++ b/NodeEditorPro/src/NodeGeometry.cpp @@ -0,0 +1,371 @@ +#include "NodeGeometry.hpp" + +#include "PortType.hpp" +#include "NodeState.hpp" +#include "NodeDataModel.hpp" +#include "Node.hpp" +#include "NodeGraphicsObject.hpp" +#include "StyleCollection.hpp" + +#include + +#include +#include + +using QtNodes::NodeGeometry; +using QtNodes::NodeDataModel; +using QtNodes::PortIndex; +using QtNodes::PortType; +using QtNodes::Node; + +NodeGeometry:: +NodeGeometry(std::unique_ptr const& dataModel) + : _width(100) + , _height(150) + , _inputPortWidth(70) + , _outputPortWidth(70) + , _entryHeight(20) + , _spacing(20) + , _hovered(false) + , _nSources(dataModel->nPorts(PortType::Out)) + , _nSinks(dataModel->nPorts(PortType::In)) + , _draggingPos(-1000, -1000) + , _dataModel(dataModel) + , _fontMetrics(QFont()) + , _boldFontMetrics(QFont()) +{ + QFont f; f.setBold(true); + + _boldFontMetrics = QFontMetrics(f); +} + +unsigned int +NodeGeometry::nSources() const +{ + return _dataModel->nPorts(PortType::Out); +} + +unsigned int +NodeGeometry::nSinks() const +{ + return _dataModel->nPorts(PortType::In); +} + +QRectF +NodeGeometry:: +entryBoundingRect() const +{ + double const addon = 0.0; + + return QRectF(0 - addon, + 0 - addon, + _entryWidth + 2 * addon, + _entryHeight + 2 * addon); +} + + +QRectF +NodeGeometry:: +boundingRect() const +{ + auto const& nodeStyle = StyleCollection::nodeStyle(); + + double addon = 4 * nodeStyle.ConnectionPointDiameter; + + return QRectF(0 - addon, + 0 - addon, + _width + 2 * addon, + _height + 2 * addon); +} + + +void +NodeGeometry:: +recalculateSize() const +{ + _entryHeight = _fontMetrics.height(); + + { + unsigned int maxNumOfEntries = std::max(_nSinks, _nSources); + unsigned int step = _entryHeight + _spacing; + _height = step * maxNumOfEntries; + } + + if (auto w = _dataModel->embeddedWidget()) + { + _height = std::max(_height, static_cast(w->height())); + } + + //_height += captionHeight(); + _height += 25; + + _inputPortWidth = portWidth(PortType::In); + _outputPortWidth = portWidth(PortType::Out); + + _width = _inputPortWidth + + _outputPortWidth + + 2 * _spacing; + + if (auto w = _dataModel->embeddedWidget()) + { + _width += w->width(); + } + + _width = std::max(_width, captionWidth()); + + if (_dataModel->validationState() != NodeValidationState::Valid) + { + _width = std::max(_width, validationWidth()); + _height += validationHeight() + _spacing; + } +} + +void +NodeGeometry:: +recalculateSize(QFont const& font) const +{ + QFontMetrics fontMetrics(font); + QFont boldFont = font; + + boldFont.setBold(true); + + QFontMetrics boldFontMetrics(boldFont); + + if (_boldFontMetrics != boldFontMetrics) + { + _fontMetrics = fontMetrics; + _boldFontMetrics = boldFontMetrics; + + recalculateSize(); + } +} + + +QPointF +NodeGeometry:: +portScenePosition(PortIndex index, + PortType portType, + QTransform const& t) const +{ + auto const& nodeStyle = StyleCollection::nodeStyle(); + + unsigned int step = _entryHeight + _spacing; + + QPointF result; + + double totalHeight = 0.0; + + totalHeight += captionHeight(); + + totalHeight += step * index; + + // TODO: why? + totalHeight += step / 2.0; + + switch (portType) + { + case PortType::Out: + { + double x = _width + nodeStyle.ConnectionPointDiameter; + + result = QPointF(x, totalHeight); + break; + } + + case PortType::In: + { + double x = 0.0 - nodeStyle.ConnectionPointDiameter; + + result = QPointF(x, totalHeight); + break; + } + + default: + break; + } + + return t.map(result); +} + + +PortIndex +NodeGeometry:: +checkHitScenePoint(PortType portType, + QPointF const scenePoint, + QTransform const& sceneTransform) const +{ + auto const& nodeStyle = StyleCollection::nodeStyle(); + + PortIndex result = INVALID; + + if (portType == PortType::None) + return result; + + double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; + + unsigned int const nItems = _dataModel->nPorts(portType); + + for (unsigned int i = 0; i < nItems; ++i) + { + auto pp = portScenePosition(i, portType, sceneTransform); + + QPointF p = pp - scenePoint; + auto distance = std::sqrt(QPointF::dotProduct(p, p)); + + if (distance < tolerance) + { + result = PortIndex(i); + break; + } + } + + return result; +} + + +QRect +NodeGeometry:: +resizeRect() const +{ + unsigned int rectSize = 7; + + return QRect(_width - rectSize, + _height - rectSize, + rectSize, + rectSize); +} + + +QPointF +NodeGeometry:: +widgetPosition() const +{ + if (auto w = _dataModel->embeddedWidget()) + { + if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) + { + // If the widget wants to use as much vertical space as possible, place it immediately after the caption. + return QPointF(_spacing + portWidth(PortType::In), captionHeight()); + } + else + { + if (_dataModel->validationState() != NodeValidationState::Valid) + { + return QPointF(_spacing + portWidth(PortType::In), + (captionHeight() + _height - validationHeight() - _spacing - w->height()) / 2.0); + } + + return QPointF(_spacing + portWidth(PortType::In), + (captionHeight() + _height - w->height()) / 2.0); + } + } + return QPointF(); +} + +int +NodeGeometry:: +equivalentWidgetHeight() const +{ + if (_dataModel->validationState() != NodeValidationState::Valid) + { + return height() - captionHeight() + validationHeight(); + } + + return height() - captionHeight(); +} + +unsigned int +NodeGeometry:: +captionHeight() const +{ + if (!_dataModel->captionVisible()) + return 0; + + QString name = _dataModel->caption(); + + return _boldFontMetrics.boundingRect(name).height(); +} + + +unsigned int +NodeGeometry:: +captionWidth() const +{ + if (!_dataModel->captionVisible()) + return 0; + + QString name = _dataModel->caption(); + + return _boldFontMetrics.boundingRect(name).width(); +} + + +unsigned int +NodeGeometry:: +validationHeight() const +{ + QString msg = _dataModel->validationMessage(); + + return _boldFontMetrics.boundingRect(msg).height(); +} + + +unsigned int +NodeGeometry:: +validationWidth() const +{ + QString msg = _dataModel->validationMessage(); + + return _boldFontMetrics.boundingRect(msg).width(); +} + + +QPointF +NodeGeometry:: +calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, Node* targetNode, + PortIndex sourcePortIndex, PortType sourcePort, Node* sourceNode, + Node& newNode) +{ + //Calculating the nodes position in the scene. It'll be positioned half way between the two ports that it "connects". + //The first line calculates the halfway point between the ports (node position + port position on the node for both nodes averaged). + //The second line offsets this coordinate with the size of the new node, so that the new nodes center falls on the originally + //calculated coordinate, instead of it's upper left corner. + auto converterNodePos = (sourceNode->nodeGraphicsObject().pos() + sourceNode->nodeGeometry().portScenePosition(sourcePortIndex, sourcePort) + + targetNode->nodeGraphicsObject().pos() + targetNode->nodeGeometry().portScenePosition(targetPortIndex, targetPort)) / 2.0f; + converterNodePos.setX(converterNodePos.x() - newNode.nodeGeometry().width() / 2.0f); + converterNodePos.setY(converterNodePos.y() - newNode.nodeGeometry().height() / 2.0f); + return converterNodePos; +} + + +unsigned int +NodeGeometry:: +portWidth(PortType portType) const +{ + unsigned width = 0; + + for (auto i = 0ul; i < _dataModel->nPorts(portType); ++i) + { + QString name; + + if (_dataModel->portCaptionVisible(portType, i)) + { + name = _dataModel->portCaption(portType, i); + } + else + { + //name = _dataModel->dataType(portType, i).name; + name = _dataModel->dataType(portType, i).name; + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + width = std::max(unsigned(_fontMetrics.horizontalAdvance(name)), + width); +#else + width = std::max(unsigned(_fontMetrics.width(name)), + width); +#endif + } + + return width; +} diff --git a/NodeEditorPro/src/NodeGraphicsObject.cpp b/NodeEditorPro/src/NodeGraphicsObject.cpp new file mode 100644 index 0000000..5296bb3 --- /dev/null +++ b/NodeEditorPro/src/NodeGraphicsObject.cpp @@ -0,0 +1,417 @@ +#include "NodeGraphicsObject.hpp" + +#include +#include + +#include +#include + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionState.hpp" + +#include "FlowScene.hpp" +#include "NodePainter.hpp" + +#include "Node.hpp" +#include "NodeDataModel.hpp" +#include "NodeConnectionInteraction.hpp" + +#include "StyleCollection.hpp" + +using QtNodes::NodeGraphicsObject; +using QtNodes::Node; +using QtNodes::FlowScene; + +NodeGraphicsObject:: +NodeGraphicsObject(FlowScene& scene, + Node& node) + : _scene(scene) + , _node(node) + , _locked(false) + , _proxyWidget(nullptr) +{ + _scene.addItem(this); + + setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true); + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true); + + setCacheMode(QGraphicsItem::DeviceCoordinateCache); + + auto const& nodeStyle = node.nodeDataModel()->nodeStyle(); + + { + auto effect = new QGraphicsDropShadowEffect; + effect->setOffset(4, 4); + effect->setBlurRadius(20); + effect->setColor(nodeStyle.ShadowColor); + + setGraphicsEffect(effect); + } + + setOpacity(nodeStyle.Opacity); + + setAcceptHoverEvents(true); + + setZValue(0); + + embedQWidget(); + + // connect to the move signals to emit the move signals in FlowScene + auto onMoveSlot = [this] { + _scene.nodeMoved(_node, pos()); + }; + connect(this, &QGraphicsObject::xChanged, this, onMoveSlot); + connect(this, &QGraphicsObject::yChanged, this, onMoveSlot); +} + + +NodeGraphicsObject:: +~NodeGraphicsObject() +{ + _scene.removeItem(this); +} + + +Node& +NodeGraphicsObject:: +node() +{ + return _node; +} + + +Node const& +NodeGraphicsObject:: +node() const +{ + return _node; +} + + +void +NodeGraphicsObject:: +embedQWidget() +{ + NodeGeometry& geom = _node.nodeGeometry(); + + if (auto w = _node.nodeDataModel()->embeddedWidget()) + { + _proxyWidget = new QGraphicsProxyWidget(this); + + _proxyWidget->setWidget(w); + + _proxyWidget->setPreferredWidth(5); + + geom.recalculateSize(); + + if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) + { + // If the widget wants to use as much vertical space as possible, set it to have the geom's equivalentWidgetHeight. + _proxyWidget->setMinimumHeight(geom.equivalentWidgetHeight()); + } + + _proxyWidget->setPos(geom.widgetPosition()); + + update(); + + _proxyWidget->setOpacity(1.0); + _proxyWidget->setFlag(QGraphicsItem::ItemIgnoresParentOpacity); + } +} + + +QRectF +NodeGraphicsObject:: +boundingRect() const +{ + return _node.nodeGeometry().boundingRect(); +} + + +void +NodeGraphicsObject:: +setGeometryChanged() +{ + prepareGeometryChange(); +} + + +void +NodeGraphicsObject:: +moveConnections() const +{ + NodeState const& nodeState = _node.nodeState(); + + for (PortType portType : {PortType::In, PortType::Out}) + { + auto const& connectionEntries = + nodeState.getEntries(portType); + + for (auto const& connections : connectionEntries) + { + for (auto& con : connections) + con.second->getConnectionGraphicsObject().move(); + } + } +} + + +void +NodeGraphicsObject:: +lock(bool locked) +{ + _locked = locked; + + setFlag(QGraphicsItem::ItemIsMovable, !locked); + setFlag(QGraphicsItem::ItemIsFocusable, !locked); + setFlag(QGraphicsItem::ItemIsSelectable, !locked); +} + + +void +NodeGraphicsObject:: +paint(QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget*) +{ + painter->setClipRect(option->exposedRect); + + NodePainter::paint(painter, _node, _scene); +} + + +QVariant +NodeGraphicsObject:: +itemChange(GraphicsItemChange change, const QVariant& value) +{ + if (change == ItemPositionChange && scene()) + { + moveConnections(); + } + + return QGraphicsItem::itemChange(change, value); +} + + +void +NodeGraphicsObject:: +mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + if (_locked) + return; + + for (PortType portToCheck : {PortType::In, PortType::Out}) + { + NodeGeometry const& nodeGeometry = _node.nodeGeometry(); + + // TODO do not pass sceneTransform + int const portIndex = nodeGeometry.checkHitScenePoint(portToCheck, + event->scenePos(), + sceneTransform()); + + if (portIndex != INVALID) + { + NodeState const& nodeState = _node.nodeState(); + + std::unordered_map connections = + nodeState.connections(portToCheck, portIndex); + + // start dragging existing connection + if (!connections.empty() && portToCheck == PortType::In) + { + auto con = connections.begin()->second; + + NodeConnectionInteraction interaction(_node, *con, _scene); + + interaction.disconnect(portToCheck); + } + else // initialize new Connection + { + if (portToCheck == PortType::Out) + { + auto const outPolicy = _node.nodeDataModel()->portOutConnectionPolicy(portIndex); + if (!connections.empty() && + outPolicy == NodeDataModel::ConnectionPolicy::One) + { + _scene.deleteConnection(*connections.begin()->second); + } + } + + // todo add to FlowScene + auto connection = _scene.createConnection(portToCheck, + _node, + portIndex); + + _node.nodeState().setConnection(portToCheck, + portIndex, + *connection); + + connection->getConnectionGraphicsObject().grabMouse(); + } + } + } + + auto pos = event->pos(); + auto& geom = _node.nodeGeometry(); + auto& state = _node.nodeState(); + + if (_node.nodeDataModel()->resizable() && + geom.resizeRect().contains(QPoint(pos.x(), + pos.y()))) + { + state.setResizing(true); + } +} + + +void +NodeGraphicsObject:: +mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + auto& geom = _node.nodeGeometry(); + auto& state = _node.nodeState(); + + // deselect all other items after this one is selected + if (!isSelected()) + { + _scene.clearSelection(); + setSelected(true); + } + + if (state.resizing()) + { + auto diff = event->pos() - event->lastPos(); + + if (auto w = _node.nodeDataModel()->embeddedWidget()) + { + prepareGeometryChange(); + + auto oldSize = w->size(); + + oldSize += QSize(diff.x(), diff.y()); + + w->setFixedSize(oldSize); + + _proxyWidget->setMinimumSize(oldSize); + _proxyWidget->setMaximumSize(oldSize); + _proxyWidget->setPos(geom.widgetPosition()); + + geom.recalculateSize(); + update(); + + moveConnections(); + + event->accept(); + } + } + else + { + QGraphicsObject::mouseMoveEvent(event); + + if (event->lastPos() != event->pos()) + moveConnections(); + + event->ignore(); + } + + QRectF r = scene()->sceneRect(); + + r = r.united(mapToScene(boundingRect()).boundingRect()); + + scene()->setSceneRect(r); +} + + +void +NodeGraphicsObject:: +mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + auto& state = _node.nodeState(); + + state.setResizing(false); + + QGraphicsObject::mouseReleaseEvent(event); + + // position connections precisely after fast node move + moveConnections(); + + _scene.nodeClicked(node()); +} + + +void +NodeGraphicsObject:: +hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + // bring all the colliding nodes to background + QList overlapItems = collidingItems(); + + for (QGraphicsItem* item : overlapItems) + { + if (item->zValue() > 0.0) + { + item->setZValue(0.0); + } + } + + // bring this node forward + setZValue(1.0); + + _node.nodeGeometry().setHovered(true); + update(); + _scene.nodeHovered(node(), event->screenPos()); + event->accept(); +} + + +void +NodeGraphicsObject:: +hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + _node.nodeGeometry().setHovered(false); + update(); + _scene.nodeHoverLeft(node()); + event->accept(); +} + + +void +NodeGraphicsObject:: +hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + auto pos = event->pos(); + auto& geom = _node.nodeGeometry(); + + if (_node.nodeDataModel()->resizable() && + geom.resizeRect().contains(QPoint(pos.x(), pos.y()))) + { + setCursor(QCursor(Qt::SizeFDiagCursor)); + } + else + { + setCursor(QCursor()); + } + + event->accept(); +} + + +void +NodeGraphicsObject:: +mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsItem::mouseDoubleClickEvent(event); + + _scene.nodeDoubleClicked(node()); +} + + +void +NodeGraphicsObject:: +contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +{ + _scene.nodeContextMenu(node(), mapToScene(event->pos())); +} diff --git a/NodeEditorPro/src/NodePainter.cpp b/NodeEditorPro/src/NodePainter.cpp new file mode 100644 index 0000000..7b09a37 --- /dev/null +++ b/NodeEditorPro/src/NodePainter.cpp @@ -0,0 +1,416 @@ +#include "NodePainter.hpp" + +#include + +#include + +#include "StyleCollection.hpp" +#include "PortType.hpp" +#include "NodeGraphicsObject.hpp" +#include "NodeGeometry.hpp" +#include "NodeState.hpp" +#include "NodeDataModel.hpp" +#include "Node.hpp" +#include "FlowScene.hpp" + +using QtNodes::NodePainter; +using QtNodes::NodeGeometry; +using QtNodes::NodeGraphicsObject; +using QtNodes::Node; +using QtNodes::NodeState; +using QtNodes::NodeDataModel; +using QtNodes::FlowScene; + +void +NodePainter:: +paint(QPainter* painter, + Node& node, + FlowScene const& scene) +{ + NodeGeometry const& geom = node.nodeGeometry(); + + NodeState const& state = node.nodeState(); + + NodeGraphicsObject const& graphicsObject = node.nodeGraphicsObject(); + + geom.recalculateSize(painter->font()); + + //-------------------------------------------- + NodeDataModel const* model = node.nodeDataModel(); + + drawNodeRect(painter, geom, model, graphicsObject); + + drawConnectionPoints(painter, geom, state, model, scene); + + drawFilledConnectionPoints(painter, geom, state, model); + + drawModelName(painter, geom, state, model); + + drawEntryLabels(painter, geom, state, model); + + drawResizeRect(painter, geom, model); + + drawValidationRect(painter, geom, model, graphicsObject); + + /// call custom painter + if (auto painterDelegate = model->painterDelegate()) + { + painterDelegate->paint(painter, geom, model); + } +} + + +void +NodePainter:: +drawNodeRect(QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject) +{ + NodeStyle const& nodeStyle = model->nodeStyle(); + + auto color = graphicsObject.isSelected() + ? nodeStyle.SelectedBoundaryColor + : nodeStyle.NormalBoundaryColor; + + if (geom.hovered()) + { + QPen p(color, nodeStyle.HoveredPenWidth); + painter->setPen(p); + } + else + { + QPen p(color, nodeStyle.PenWidth); + painter->setPen(p); + } + + painter->setBrush(nodeStyle.BackgroundColor); + + float diam = nodeStyle.ConnectionPointDiameter; + + QRectF boundary(-diam, -diam, 2.0 * diam + geom.width(), 2.0 * diam + geom.height()); + + qreal const radius = 3.0; + + painter->drawRoundedRect(boundary, radius, radius); + + //绘制标题栏 + painter->setBrush(nodeStyle.TitleColor); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(-diam + 1, -diam + 1, 2.0 * diam + geom.width() - 2, 25, radius, radius); + +} + + +void +NodePainter:: +drawConnectionPoints(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model, + FlowScene const& scene) +{ + NodeStyle const& nodeStyle = model->nodeStyle(); + auto const& connectionStyle = StyleCollection::connectionStyle(); + + float diameter = nodeStyle.ConnectionPointDiameter; + auto reducedDiameter = diameter * 0.6; + + for (PortType portType : {PortType::Out, PortType::In}) + { + size_t n = state.getEntries(portType).size(); + + for (unsigned int i = 0; i < n; ++i) + { + QPointF p = geom.portScenePosition(i, portType); + + auto const& dataType = + model->dataType(portType, static_cast(i)); + + bool canConnect = (state.getEntries(portType)[i].empty() || + (portType == PortType::Out && + model->portOutConnectionPolicy(i) == NodeDataModel::ConnectionPolicy::Many)); + + double r = 1.0; + if (state.isReacting() && + canConnect && + portType == state.reactingPortType()) + { + + auto diff = geom.draggingPos() - p; + double dist = std::sqrt(QPointF::dotProduct(diff, diff)); + bool typeConvertable = false; + + { + if (portType == PortType::In) + { + typeConvertable = scene.registry().getTypeConverter(state.reactingDataType(), dataType) != nullptr; + } + else + { + typeConvertable = scene.registry().getTypeConverter(dataType, state.reactingDataType()) != nullptr; + } + } + + if (state.reactingDataType().id == dataType.id || typeConvertable) + { + double const thres = 40.0; + r = (dist < thres) ? + (2.0 - dist / thres) : + 1.0; + } + else + { + double const thres = 80.0; + r = (dist < thres) ? + (dist / thres) : + 1.0; + } + } + + if (connectionStyle.useDataDefinedColors()) + { + painter->setBrush(connectionStyle.normalColor(dataType.id)); + } + else + { + painter->setBrush(nodeStyle.ConnectionPointColor); + } + + painter->drawEllipse(p, + reducedDiameter * r, + reducedDiameter * r); + } + }; +} + + +void +NodePainter:: +drawFilledConnectionPoints(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model) +{ + NodeStyle const& nodeStyle = model->nodeStyle(); + auto const& connectionStyle = StyleCollection::connectionStyle(); + + auto diameter = nodeStyle.ConnectionPointDiameter; + + for (PortType portType : {PortType::Out, PortType::In}) + { + size_t n = state.getEntries(portType).size(); + + for (size_t i = 0; i < n; ++i) + { + QPointF p = geom.portScenePosition( + static_cast(i), + static_cast(portType)); + + if (!state.getEntries(portType)[i].empty()) + { + auto const& dataType = + model->dataType(portType, static_cast(i)); + + if (connectionStyle.useDataDefinedColors()) + { + QColor const c = connectionStyle.normalColor(dataType.id); + painter->setPen(c); + painter->setBrush(c); + } + else + { + painter->setPen(nodeStyle.FilledConnectionPointColor); + painter->setBrush(nodeStyle.FilledConnectionPointColor); + } + + painter->drawEllipse(p, + diameter * 0.4, + diameter * 0.4); + } + } + } +} + + +void +NodePainter:: +drawModelName(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model) +{ + NodeStyle const& nodeStyle = model->nodeStyle(); + + Q_UNUSED(state); + + if (!model->captionVisible()) + return; + + QString const& name = model->caption(); + + QFont f = painter->font(); + + f.setBold(true); + + QFontMetrics metrics(f); + + auto rect = metrics.boundingRect(name); + + QPointF position((geom.width() - rect.width()) / 2.0, + (geom.spacing() + geom.entryHeight()) / 3.0); + + painter->setFont(f); + painter->setPen(nodeStyle.FontColor); + painter->drawText(position, name); + + f.setBold(false); + painter->setFont(f); +} + + +void +NodePainter:: +drawEntryLabels(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model) +{ + QFontMetrics const& metrics = + painter->fontMetrics(); + + for (PortType portType : {PortType::Out, PortType::In}) + { + auto const& nodeStyle = model->nodeStyle(); + + auto& entries = state.getEntries(portType); + + size_t n = entries.size(); + + for (size_t i = 0; i < n; ++i) + { + QPointF p = geom.portScenePosition(static_cast(i), portType); + + if (entries[i].empty()) + painter->setPen(nodeStyle.FontColorFaded); + else + painter->setPen(nodeStyle.FontColor); + + QString s; + + if (model->portCaptionVisible(portType, static_cast(i))) + { + s = model->portCaption(portType, static_cast(i)); + } + else + { + s = model->dataType(portType, static_cast(i)).name; + } + + auto rect = metrics.boundingRect(s); + + p.setY(p.y() + rect.height() / 4.0); + + switch (portType) + { + case PortType::In: + p.setX(5.0); + break; + + case PortType::Out: + p.setX(geom.width() - 5.0 - rect.width()); + break; + + default: + break; + } + + painter->drawText(p, s); + } + } +} + + +void +NodePainter:: +drawResizeRect(QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model) +{ + if (model->resizable()) + { + painter->setBrush(Qt::gray); + + painter->drawEllipse(geom.resizeRect()); + } +} + + +void +NodePainter:: +drawValidationRect(QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject) +{ + auto modelValidationState = model->validationState(); + + if (modelValidationState != NodeValidationState::Valid) + { + NodeStyle const& nodeStyle = model->nodeStyle(); + + auto color = graphicsObject.isSelected() + ? nodeStyle.SelectedBoundaryColor + : nodeStyle.NormalBoundaryColor; + + if (geom.hovered()) + { + QPen p(color, nodeStyle.HoveredPenWidth); + painter->setPen(p); + } + else + { + QPen p(color, nodeStyle.PenWidth); + painter->setPen(p); + } + + //Drawing the validation message background + if (modelValidationState == NodeValidationState::Error) + { + painter->setBrush(nodeStyle.ErrorColor); + } + else + { + painter->setBrush(nodeStyle.WarningColor); + } + + double const radius = 3.0; + + float diam = nodeStyle.ConnectionPointDiameter; + + QRectF boundary(-diam, + -diam + geom.height() - geom.validationHeight(), + 2.0 * diam + geom.width(), + 2.0 * diam + geom.validationHeight()); + + painter->drawRoundedRect(boundary, radius, radius); + + painter->setBrush(Qt::gray); + + //Drawing the validation message itself + QString const& errorMsg = model->validationMessage(); + + QFont f = painter->font(); + + QFontMetrics metrics(f); + + auto rect = metrics.boundingRect(errorMsg); + + QPointF position((geom.width() - rect.width()) / 2.0, + geom.height() - (geom.validationHeight() - diam) / 2.0); + + painter->setFont(f); + painter->setPen(nodeStyle.FontColor); + painter->drawText(position, errorMsg); + } +} diff --git a/NodeEditorPro/src/NodePainter.hpp b/NodeEditorPro/src/NodePainter.hpp new file mode 100644 index 0000000..fe8c691 --- /dev/null +++ b/NodeEditorPro/src/NodePainter.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +namespace QtNodes +{ + + class Node; + class NodeState; + class NodeGeometry; + class NodeGraphicsObject; + class NodeDataModel; + class FlowItemEntry; + class FlowScene; + + class NodePainter + { + public: + + NodePainter(); + + public: + + static + void + paint(QPainter* painter, + Node& node, + FlowScene const& scene); + + static + void + drawNodeRect(QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject); + + static + void + drawModelName(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model); + + static + void + drawEntryLabels(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model); + + static + void + drawConnectionPoints(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model, + FlowScene const& scene); + + static + void + drawFilledConnectionPoints(QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model); + + static + void + drawResizeRect(QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model); + + static + void + drawValidationRect(QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject); + }; +} diff --git a/NodeEditorPro/src/NodeState.cpp b/NodeEditorPro/src/NodeState.cpp new file mode 100644 index 0000000..2659f2b --- /dev/null +++ b/NodeEditorPro/src/NodeState.cpp @@ -0,0 +1,138 @@ +#include "NodeState.hpp" + +#include "NodeDataModel.hpp" + +#include "Connection.hpp" + +using QtNodes::NodeState; +using QtNodes::NodeDataType; +using QtNodes::NodeDataModel; +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::Connection; + +NodeState:: +NodeState(std::unique_ptr const& model) + : _inConnections(model->nPorts(PortType::In)) + , _outConnections(model->nPorts(PortType::Out)) + , _reaction(NOT_REACTING) + , _reactingPortType(PortType::None) + , _resizing(false) +{} + + +std::vector const& +NodeState:: +getEntries(PortType portType) const +{ + if (portType == PortType::In) + return _inConnections; + else + return _outConnections; +} + + +std::vector& +NodeState:: +getEntries(PortType portType) +{ + if (portType == PortType::In) + return _inConnections; + else + return _outConnections; +} + + +NodeState::ConnectionPtrSet +NodeState:: +connections(PortType portType, PortIndex portIndex) const +{ + auto const& connections = getEntries(portType); + + return connections[portIndex]; +} + + +void +NodeState:: +setConnection(PortType portType, + PortIndex portIndex, + Connection& connection) +{ + auto& connections = getEntries(portType); + + connections.at(portIndex).insert(std::make_pair(connection.id(), + &connection)); +} + + +void +NodeState:: +eraseConnection(PortType portType, + PortIndex portIndex, + QUuid id) +{ + getEntries(portType)[portIndex].erase(id); +} + + +NodeState::ReactToConnectionState +NodeState:: +reaction() const +{ + return _reaction; +} + + +PortType +NodeState:: +reactingPortType() const +{ + return _reactingPortType; +} + + +NodeDataType +NodeState:: +reactingDataType() const +{ + return _reactingDataType; +} + + +void +NodeState:: +setReaction(ReactToConnectionState reaction, + PortType reactingPortType, + NodeDataType reactingDataType) +{ + _reaction = reaction; + + _reactingPortType = reactingPortType; + + _reactingDataType = std::move(reactingDataType); +} + + +bool +NodeState:: +isReacting() const +{ + return _reaction == REACTING; +} + + +void +NodeState:: +setResizing(bool resizing) +{ + _resizing = resizing; +} + + +bool +NodeState:: +resizing() const +{ + return _resizing; +} diff --git a/NodeEditorPro/src/NodeStyle.cpp b/NodeEditorPro/src/NodeStyle.cpp new file mode 100644 index 0000000..043d064 --- /dev/null +++ b/NodeEditorPro/src/NodeStyle.cpp @@ -0,0 +1,79 @@ +#include "NodeStyle.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include "StyleCollection.hpp" +#include "QJsonParser.hpp" + +using QtNodes::NodeStyle; + +inline void initResources() { Q_INIT_RESOURCE(resources); } + +NodeStyle:: +NodeStyle() : + NormalBoundaryColor(255, 255, 255, 255), + SelectedBoundaryColor(255, 165, 0, 255), + BackgroundColor(34, 34, 34, 255), + TitleColor(253, 204, 82, 255), + GradientColor0(60, 60, 60, 255), + GradientColor1(80, 80, 80, 255), + GradientColor2(64, 64, 64, 255), + GradientColor3(58, 58, 58, 255), + ShadowColor(20, 20, 20, 255), + FontColor(255, 255, 255, 255), + FontColorFaded(120, 120, 120, 255), + ConnectionPointColor(169, 169, 169, 255), + FilledConnectionPointColor(0, 255, 255, 255), + WarningColor(128, 128, 0, 255), + ErrorColor(255, 0, 0, 255), + PenWidth(1.0), + HoveredPenWidth(1.5), + ConnectionPointDiameter(8.0), + Opacity(0.8) +{ + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile("DefaultStyle.json"); +} + +NodeStyle:: +NodeStyle(QString fileName) +{ + loadJsonFile(fileName); +} + +void +NodeStyle:: +setNodeStyle(QString fileName) +{ + NodeStyle style(fileName); + StyleCollection::setNodeStyle(style); +} + +void +NodeStyle:: +loadJsonFile(QString styleFile) +{ + QJsonObject obj = QJsonConvert::readJsonObj(styleFile); + QJsonObject styleObj = obj["NodeStyle"].toObject(); + if (styleObj.isEmpty()) + { + saveJsonFile(styleFile); + } + else + { + QJsonConvert::convertFromJson(obj["NodeStyle"].toObject(), *this); + } +} + +void QtNodes::NodeStyle::saveJsonFile(QString fileName) +{ + QJsonObject obj = QJsonConvert::readJsonObj(fileName); + obj.insert("NodeStyle", QJsonConvert::convertToJson(*this)); + QJsonConvert::writeJsonObj(fileName, obj); +} diff --git a/NodeEditorPro/src/Properties.cpp b/NodeEditorPro/src/Properties.cpp new file mode 100644 index 0000000..10f2101 --- /dev/null +++ b/NodeEditorPro/src/Properties.cpp @@ -0,0 +1,12 @@ +#include "Properties.hpp" + +using QtNodes::Properties; + +void +Properties:: +put(QString const& name, QVariant const& v) +{ + _values.insert(name, v); +} + + diff --git a/NodeEditorPro/src/Properties.hpp b/NodeEditorPro/src/Properties.hpp new file mode 100644 index 0000000..83541b2 --- /dev/null +++ b/NodeEditorPro/src/Properties.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include + +#include "Export.hpp" + +namespace QtNodes +{ + + class Properties + { + public: + + void + put(QString const& name, QVariant const& v); + + template + bool + get(QString name, T* v) const + { + QVariant const& var = _values[name]; + + if (var.canConvert()) + { + *v = _values[name].value(); + + return true; + } + + return false; + } + + QVariantMap const& + values() const + { + return _values; + } + + QVariantMap& + values() + { + return _values; + } + + private: + + QVariantMap _values; + }; +} diff --git a/NodeEditorPro/src/QDataStreamPhaser.cpp b/NodeEditorPro/src/QDataStreamPhaser.cpp new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/NodeEditorPro/src/QDataStreamPhaser.cpp @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/NodeEditorPro/src/QDataStreamPhaser.hpp b/NodeEditorPro/src/QDataStreamPhaser.hpp new file mode 100644 index 0000000..982801a --- /dev/null +++ b/NodeEditorPro/src/QDataStreamPhaser.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include +#include "halconcpp/HalconCpp.h" + +template +inline void loadFromData(const QString _fileName, std::vector& _obj); + +template +inline void saveToData(const QString _fileName, const std::vector& _obj); + diff --git a/NodeEditorPro/src/QJsonParser.cpp b/NodeEditorPro/src/QJsonParser.cpp new file mode 100644 index 0000000..0229578 --- /dev/null +++ b/NodeEditorPro/src/QJsonParser.cpp @@ -0,0 +1,274 @@ +#include "QJsonParser.hpp" +#include +#include +#include +#include + +QJsonObject QJsonConvert::convertToJson(const QVector& obj) +{ + QJsonObject result; + QJsonArray arr; + for (auto& elem : obj) + { + arr.append(convertToJson(elem)); + } + result.insert("data", arr); + return result; +} + +void QJsonConvert::convertFromJson(const QJsonObject& json, QVector& obj) +{ + auto jarr = json["data"].toArray(); + for (auto elem : jarr) + { + QPolygonF tmp_data; + QJsonConvert::convertFromJson(elem.toObject(), tmp_data); + obj.push_back(tmp_data); + } +} + +QJsonObject QJsonConvert::convertToJson(const QVector& obj) +{ + QJsonObject result; + QJsonArray arr; + for (auto& elem : obj) + { + arr.append((int)elem); + } + result.insert("data", arr); + return result; +} + +void QJsonConvert::convertFromJson(const QJsonObject& json, QVector& obj) +{ + auto jarr = json["data"].toArray(); + for (auto elem : jarr) + { + ShapeMode tmp_data; + tmp_data = (ShapeMode)elem.toInt(0); + obj.push_back(tmp_data); + } +} + +QJsonObject QJsonConvert::convertToJson(const QVector& obj) +{ + QJsonObject result; + QJsonArray arr; + for (auto& elem : obj) + { + arr.append(elem); + } + result.insert("data", arr); + return result; +} + +void QJsonConvert::convertFromJson(const QJsonObject& json, QVector& obj) +{ + auto jarr = json["data"].toArray(); + for (auto elem : jarr) + { + int tmp_data; + tmp_data = elem.toInt(0); + obj.push_back(tmp_data); + } +} + +QJsonObject QJsonConvert::readJsonObj(const QString& fileName) +{ + QFile file; + + file.setFileName(fileName); + + if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) + return QJsonObject(); + + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + if (doc.isEmpty() || !doc.isObject()) + return QJsonObject(); + + return doc.object(); +} +bool QJsonConvert::writeJsonObj(const QString& fileName, QJsonObject& json) +{ + QJsonDocument doc; + QFile file; + + file.setFileName(fileName); + + if (!file.open(QIODevice::Text | QIODevice::WriteOnly | QIODevice::Truncate)) + return false; + + doc.setObject(json); + auto bytes = doc.toJson(); + + return ( + !bytes.isEmpty() && + file.write(bytes) == bytes.size()); +} + +QJsonObject QJsonConvert::convertToJson(const QPoint& _point) +{ + QJsonObject result; + result.insert("x", _point.x()); + result.insert("y", _point.y()); + return result; +} +void QJsonConvert::convertFromJson(const QJsonObject& _obj, QPoint& _point) +{ + _point.setX(_obj.value("x").toInt()); + _point.setY(_obj.value("y").toInt()); +} + +QJsonObject QJsonConvert::convertToJson(const QPointF& _point) +{ + QJsonObject result; + result.insert("x", _point.x()); + result.insert("y", _point.y()); + return result; +} +void QJsonConvert::convertFromJson(const QJsonObject& _obj, QPointF& _point) +{ + _point.setX(_obj.value("x").toDouble()); + _point.setY(_obj.value("y").toDouble()); +} + +QJsonObject QJsonConvert::convertToJson(const QPolygonF& _polygon) +{ + QJsonObject result; + QJsonArray json_array; + for (auto& elem : _polygon) + { + json_array.append(convertToJson(elem)); + } + result.insert("poly_data", json_array); + return result; +} + +void QJsonConvert::convertFromJson(const QJsonObject& _obj, QPolygonF& _polygon) +{ + _polygon.clear(); + QJsonArray array = _obj["poly_data"].toArray(); + for (auto elem : array) + { + QPointF tmp_point; + convertFromJson(elem.toObject(), tmp_point); + _polygon.push_back(tmp_point); + } +} + +QJsonObject QJsonConvert::convertToJson(const QColor& _color) +{ + QJsonObject result; + result.insert("red", _color.red()); + result.insert("green", _color.green()); + result.insert("blue", _color.blue()); + result.insert("alpha", _color.alpha()); + return result; +} +void QJsonConvert::convertFromJson(const QJsonObject& _obj, QColor& _color) +{ + _color = QColor( + _obj.value("red").toInt(), + _obj.value("green").toInt(), + _obj.value("blue").toInt(), + _obj.value("alpha").toInt()); +} + +QJsonObject QJsonConvert::convertToJson(const QtNodes::NodeStyle& _obj) +{ + QJsonObject result; + result.insert("NormalBoundaryColor", convertToJson(_obj.NormalBoundaryColor)); + result.insert("SelectedBoundaryColor", convertToJson(_obj.SelectedBoundaryColor)); + result.insert("BackgroundColor", convertToJson(_obj.BackgroundColor)); + result.insert("TitleColor", convertToJson(_obj.TitleColor)); + result.insert("GradientColor0", convertToJson(_obj.GradientColor0)); + result.insert("GradientColor1", convertToJson(_obj.GradientColor1)); + result.insert("GradientColor2", convertToJson(_obj.GradientColor2)); + result.insert("GradientColor3", convertToJson(_obj.GradientColor3)); + result.insert("ShadowColor", convertToJson(_obj.ShadowColor)); + result.insert("FontColor", convertToJson(_obj.FontColor)); + result.insert("FontColorFaded", convertToJson(_obj.FontColorFaded)); + result.insert("ConnectionPointColor", convertToJson(_obj.ConnectionPointColor)); + result.insert("FilledConnectionPointColor", convertToJson(_obj.FilledConnectionPointColor)); + result.insert("WarningColor", convertToJson(_obj.WarningColor)); + result.insert("ErrorColor", convertToJson(_obj.ErrorColor)); + result.insert("PenWidth", _obj.PenWidth); + result.insert("HoveredPenWidth", _obj.HoveredPenWidth); + result.insert("ConnectionPointDiameter", _obj.ConnectionPointDiameter); + result.insert("Opacity", _obj.Opacity); + return result; +} +void QJsonConvert::convertFromJson(const QJsonObject& _json, QtNodes::NodeStyle& _obj) +{ + convertFromJson(_json["NormalBoundaryColor"].toObject(), _obj.NormalBoundaryColor); + convertFromJson(_json["SelectedBoundaryColor"].toObject(), _obj.SelectedBoundaryColor); + convertFromJson(_json["BackgroundColor"].toObject(), _obj.BackgroundColor); + convertFromJson(_json["TitleColor"].toObject(), _obj.TitleColor); + convertFromJson(_json["GradientColor0"].toObject(), _obj.GradientColor0); + convertFromJson(_json["GradientColor1"].toObject(), _obj.GradientColor1); + convertFromJson(_json["GradientColor2"].toObject(), _obj.GradientColor2); + convertFromJson(_json["GradientColor3"].toObject(), _obj.GradientColor3); + convertFromJson(_json["ShadowColor"].toObject(), _obj.ShadowColor); + convertFromJson(_json["FontColor"].toObject(), _obj.FontColor); + convertFromJson(_json["FontColorFaded"].toObject(), _obj.FontColorFaded); + convertFromJson(_json["ConnectionPointColor"].toObject(), _obj.ConnectionPointColor); + convertFromJson(_json["FilledConnectionPointColor"].toObject(), _obj.FilledConnectionPointColor); + convertFromJson(_json["WarningColor"].toObject(), _obj.WarningColor); + convertFromJson(_json["ErrorColor"].toObject(), _obj.ErrorColor); + + _obj.PenWidth = _json["PenWidth"].toDouble(0); + _obj.HoveredPenWidth = _json["HoveredPenWidth"].toDouble(0); + _obj.ConnectionPointDiameter = _json["ConnectionPointDiameter"].toDouble(0); + _obj.Opacity = _json["Opacity"].toDouble(0); + +} + +QJsonObject QJsonConvert::convertToJson(const QtNodes::FlowViewStyle& _obj) +{ + QJsonObject result; + result.insert("BackgroundColor", convertToJson(_obj.BackgroundColor)); + result.insert("CoarseGridColor", convertToJson(_obj.CoarseGridColor)); + result.insert("FineGridColor", convertToJson(_obj.FineGridColor)); + return result; +} +void QJsonConvert::convertFromJson(const QJsonObject& _json, QtNodes::FlowViewStyle& _obj) +{ + convertFromJson(_json["BackgroundColor"].toObject(), _obj.BackgroundColor); + convertFromJson(_json["CoarseGridColor"].toObject(), _obj.CoarseGridColor); + convertFromJson(_json["FineGridColor"].toObject(), _obj.FineGridColor); +} + +QJsonObject QJsonConvert::convertToJson(const QtNodes::ConnectionStyle& _obj) +{ + QJsonObject result; + result.insert("BackgroundColor", convertToJson(_obj.ConstructionColor)); + result.insert("NormalColor", convertToJson(_obj.NormalColor)); + result.insert("SelectedColor", convertToJson(_obj.SelectedColor)); + result.insert("SelectedHaloColor", convertToJson(_obj.SelectedHaloColor)); + result.insert("HoveredColor", convertToJson(_obj.HoveredColor)); + + result.insert("LineWidth", _obj.LineWidth); + result.insert(" ructionLineWidth", _obj.ConstructionLineWidth); + result.insert("PointDiameter", _obj.PointDiameter); + result.insert("UseDataDefinedColors", _obj.UseDataDefinedColors); + + return result; +} +void QJsonConvert::convertFromJson(const QJsonObject& _json, QtNodes::ConnectionStyle& _obj) +{ +} + +QJsonObject QJsonConvert::convertToJson(const ShapeDataStruct& _obj) +{ + QJsonObject result; + result.insert("shapePolygon", convertToJson(_obj.shapePolygon)); + result.insert("shapeMode", convertToJson(_obj.shapeMode)); + return result; +} + +void QJsonConvert::convertFromJson(const QJsonObject& _json, ShapeDataStruct& _obj) +{ + convertFromJson(_json.value("shapePolygon").toObject(), _obj.shapePolygon); + convertFromJson(_json.value("shapeMode").toObject(), _obj.shapeMode); + +} diff --git a/NodeEditorPro/src/QJsonParser.hpp b/NodeEditorPro/src/QJsonParser.hpp new file mode 100644 index 0000000..1acbc39 --- /dev/null +++ b/NodeEditorPro/src/QJsonParser.hpp @@ -0,0 +1,58 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "halconcpp/HalconCpp.h" +#include "NodeStyle.hpp" +#include "FlowViewStyle.hpp" +#include "ConnectionStyle.hpp" +#include "DrawShapeView.hpp" + +namespace QJsonConvert +{ + + QJsonObject convertToJson(const QVector& obj); + void convertFromJson(const QJsonObject& json, QVector& obj); + + QJsonObject convertToJson(const QVector& obj); + void convertFromJson(const QJsonObject& json, QVector& obj); + + QJsonObject convertToJson(const QVector& obj); + void convertFromJson(const QJsonObject& json, QVector& obj); + + QJsonObject readJsonObj(const QString& fileName); + bool writeJsonObj(const QString& fileName, QJsonObject& json); + + QJsonObject convertToJson(const QPoint& _point); + void convertFromJson(const QJsonObject& _obj, QPoint& _point); + + QJsonObject convertToJson(const QPointF& _point); + void convertFromJson(const QJsonObject& _obj, QPointF& _point); + + QJsonObject convertToJson(const QPolygonF& _polygon); + void convertFromJson(const QJsonObject& _obj, QPolygonF& _polygon); + + QJsonObject convertToJson(const QColor& _color); + void convertFromJson(const QJsonObject& _obj, QColor& _color); + + QJsonObject convertToJson(const QtNodes::NodeStyle& _obj); + void convertFromJson(const QJsonObject& _json, QtNodes::NodeStyle& _obj); + + QJsonObject convertToJson(const QtNodes::FlowViewStyle& _obj); + void convertFromJson(const QJsonObject& _json, QtNodes::FlowViewStyle& _obj); + + QJsonObject convertToJson(const QtNodes::ConnectionStyle& _obj); + void convertFromJson(const QJsonObject& _json, QtNodes::ConnectionStyle& _obj); + + QJsonObject convertToJson(const ShapeDataStruct& _obj); + void convertFromJson(const QJsonObject& _json, ShapeDataStruct& _obj); + +}; + diff --git a/NodeEditorPro/src/StyleCollection.cpp b/NodeEditorPro/src/StyleCollection.cpp new file mode 100644 index 0000000..f911b1d --- /dev/null +++ b/NodeEditorPro/src/StyleCollection.cpp @@ -0,0 +1,64 @@ +#include "StyleCollection.hpp" + +using QtNodes::StyleCollection; +using QtNodes::NodeStyle; +using QtNodes::ConnectionStyle; +using QtNodes::FlowViewStyle; + +NodeStyle const& +StyleCollection:: +nodeStyle() +{ + return instance()._nodeStyle; +} + + +ConnectionStyle const& +StyleCollection:: +connectionStyle() +{ + return instance()._connectionStyle; +} + + +FlowViewStyle const& +StyleCollection:: +flowViewStyle() +{ + return instance()._flowViewStyle; +} + + +void +StyleCollection:: +setNodeStyle(NodeStyle nodeStyle) +{ + instance()._nodeStyle = nodeStyle; +} + + +void +StyleCollection:: +setConnectionStyle(ConnectionStyle connectionStyle) +{ + instance()._connectionStyle = connectionStyle; +} + + +void +StyleCollection:: +setFlowViewStyle(FlowViewStyle flowViewStyle) +{ + instance()._flowViewStyle = flowViewStyle; +} + + + +StyleCollection& +StyleCollection:: +instance() +{ + static StyleCollection collection; + + return collection; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8537ca --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# VisionFlowPro +NodeEditor base on qt halcon c++ ,thanks to this great project https://github.com/paceholder/nodeeditor . I had added some custom feature,and include some halcon nodes. +In the near future i might make it a industrial machine vision software like VisionMaster.finger cross! +by susigo@foxmail.com + +## 一、项目启发 +自从去年用了海康的VisionMaster后,做一款自己的拖拉拽机器视觉框架的想法就一直萦绕在我心中。所以在后续的时间里我在C#与C++中纠结,接触了大神开源的STNode后,被winform的难搞的绘图方式劝退。Qt有它独特的QGraphicsView框架,图元可以很方便地作为节点进行移动。因为找到的资料很少,恶心的CSDN什么都收费,当时自己摸索做了一个,使用void*指针传值,也可以勉强完成节点间传输数据的任务。后来C++水平提升,终于看懂了大神开源的框架。于是兴奋地通宵上传这个工程。本着开源精神,希望能够抛砖引玉。 +如有指教可联系qq:2576662787 + +## 二、更新日志 +*** +1.更改了绘制图形选区的方式,使用graphicsview。 + +![reducedomain showcase](https://github.com/susigo/VisionFlowPro/blob/master/NodeEditorPro/showcase/draw_shape_view.png) + +*** +1.完成了一个halcon样例。小demo。 + +![reducedomain showcase](https://github.com/susigo/VisionFlowPro/blob/master/NodeEditorPro/showcase/selectBallTest.png) + +2022/09/30 +*** +1.更新了选区节点,现在可以坐下一步操作了。如绘制区域训练片训练匹配模型。 + +![reducedomain showcase](https://github.com/susigo/VisionFlowPro/blob/master/NodeEditorPro/showcase/ReduceDomain.gif) + +2022/09/29 +*** +1. 可以保存项目与读取项目 +2. 添加了SelectRegion的节点、添加了Connection节点。 + +![调通了halcon节点保存的逻辑](https://github.com/susigo/VisionFlowPro/blob/master/NodeEditorPro/showcase/showcase4.gif) + +2022/09/23 + +*** + diff --git a/ShapeDrawer/DrawShapeView.cpp b/ShapeDrawer/DrawShapeView.cpp new file mode 100644 index 0000000..3d5b34f --- /dev/null +++ b/ShapeDrawer/DrawShapeView.cpp @@ -0,0 +1,504 @@ + +#include "DrawShapeView.hpp" +#include + +DrawShapeView* DrawShapeView::instance = nullptr; + +DrawShapeView::DrawShapeView(QWidget* parent) : + m_scene(new QGraphicsScene()) +{ + if (parent != Q_NULLPTR) + { + this->setParent(parent); + } + m_scene->setSceneRect(-3000, -3000, 6000, 6000); + this->setScene(m_scene); + this->resize(1204, 720); + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setRenderHint(QPainter::Antialiasing); + this->setMouseTracking(true); + /*以鼠标中心进行缩放*/ + setTransformationAnchor(QGraphicsView::AnchorUnderMouse);//设置视口变换的锚点,这个属性控制当视图做变换时应该如何摆放场景的位置 + setResizeAnchor(QGraphicsView::AnchorUnderMouse); + MenuInit(); + ParamInit(); + +} + +DrawShapeView* DrawShapeView::getInst() +{ + draw_view_lock.lock(); + if (DrawShapeView::instance == nullptr) + { + DrawShapeView::instance = new DrawShapeView(); + } + draw_view_lock.unlock(); + return DrawShapeView::instance; +} + +void DrawShapeView::FitShowImage(const QPixmap& pixmap) +{ + if (pixmap != m_cur_pixmap) + { + m_cur_pixmap = pixmap; + } + m_pixmap_item->setPixmap(m_cur_pixmap); + qreal w_ratio = 1.0 * this->width() / m_cur_pixmap.width(); + qreal h_ratio = 1.0 * this->height() / m_cur_pixmap.height(); + if (w_ratio > h_ratio) + { + m_scale = h_ratio; + } + else + { + m_scale = w_ratio; + } + + m_transform.reset(); + + m_transform.scale(m_scale, m_scale); + this->setTransform(m_transform); + m_centerPos = QPointF(m_cur_pixmap.width() * 0.5, m_cur_pixmap.height() * 0.5); + centerOn(m_centerPos); +} + +void DrawShapeView::FitShowImage(const QPixmap& pixmap, ShapeDataStruct& _shape_data) +{ + //shape_data = _shape_data; + for (auto elem : shape_items) + { + m_scene->removeItem(elem); + } + shape_items.clear(); + cur_shape_item = nullptr; + FitShowImage(pixmap); + this->show(); +} + +HalconCpp::HRegion DrawShapeView::GetHRegionFromData(const ShapeDataStruct& shape_data) +{ + HalconCpp::HRegion region_add; + HalconCpp::HRegion region_result; + HalconCpp::GenEmptyRegion(®ion_result); + + for (size_t i = 0; i < shape_data.shapePolygon.size(); i++) + { + HalconCpp::HTuple Hrow; + HalconCpp::HTuple Hcol; + for (int j = 0; j < shape_data.shapePolygon[i].length(); j++) + { + Hrow.Append(shape_data.shapePolygon[i][j].y()); + Hcol.Append(shape_data.shapePolygon[i][j].x()); + } + + region_add.GenRegionPolygonFilled(Hrow, Hcol); + + if (shape_data.shapeMode[i] == ShapeMode::mAdd) + { + HalconCpp::Union2(region_add, region_result, ®ion_result); + } + else + { + HalconCpp::Difference(region_result, region_add, ®ion_result); + } + } + return region_result; +} + +void DrawShapeView::MenuInit() +{ + m_menu = new QMenu(QStringLiteral("右键菜单"), this); + QAction* actOpenImage = new QAction(QStringLiteral("选择图片")); + QAction* actFitImage = new QAction(QStringLiteral("重置显示")); + QAction* actDrawLine = new QAction(QStringLiteral("绘制直线")); + QMenu* mAddShape = new QMenu(QStringLiteral("相加图形")); + QAction* actDrawRectangle1Add = new QAction(QStringLiteral("正矩形")); + QAction* actDrawRectangle2Add = new QAction(QStringLiteral("斜矩形")); + QAction* actDrawPolygonAdd = new QAction(QStringLiteral("多边形")); + QAction* actDrawFreeDrawAdd = new QAction(QStringLiteral("涂鸦")); + mAddShape->addAction(actDrawRectangle1Add); + mAddShape->addAction(actDrawRectangle2Add); + mAddShape->addAction(actDrawPolygonAdd); + mAddShape->addAction(actDrawFreeDrawAdd); + + QMenu* mDivShape = new QMenu(QStringLiteral("相减图形")); + QAction* actDrawRectangle1Div = new QAction(QStringLiteral("正矩形")); + QAction* actDrawRectangle2Div = new QAction(QStringLiteral("斜矩形")); + QAction* actDrawPolygonDiv = new QAction(QStringLiteral("多边形")); + QAction* actDrawFreeDrawDiv = new QAction(QStringLiteral("涂鸦")); + mDivShape->addAction(actDrawRectangle1Div); + mDivShape->addAction(actDrawRectangle2Div); + mDivShape->addAction(actDrawPolygonDiv); + mDivShape->addAction(actDrawFreeDrawDiv); + + QAction* actComform = new QAction(QStringLiteral("确认")); + QAction* actCancel = new QAction(QStringLiteral("取消")); + m_menu->addAction(actOpenImage); + m_menu->addAction(actFitImage); + m_menu->addAction(actDrawLine); + m_menu->addMenu(mAddShape); + m_menu->addMenu(mDivShape); + m_menu->addAction(actComform); + m_menu->addAction(actCancel); + + connect(actOpenImage, SIGNAL(triggered()), this, SLOT(onOpenImage())); + connect(actFitImage, SIGNAL(triggered()), this, SLOT(onFitImageShow())); + connect(actDrawLine, SIGNAL(triggered()), this, SLOT(onDrawLineShape())); + + connect(actDrawRectangle1Add, &QAction::triggered, this, [=]() { + this->onDrawRectangle1(ShapeMode::mAdd); }); + connect(actDrawRectangle2Add, &QAction::triggered, this, [=]() { + this->onDrawRectangle2(ShapeMode::mAdd); }); + connect(actDrawPolygonAdd, &QAction::triggered, this, [=]() { + this->onDrawPolygon(ShapeMode::mAdd); }); + connect(actDrawFreeDrawAdd, &QAction::triggered, this, [=]() { + this->onDrawFreeDraw(ShapeMode::mAdd); }); + + connect(actDrawRectangle1Div, &QAction::triggered, this, [=]() { + this->onDrawRectangle1(ShapeMode::mDiv); }); + connect(actDrawRectangle2Div, &QAction::triggered, this, [=]() { + this->onDrawRectangle2(ShapeMode::mDiv); }); + connect(actDrawPolygonDiv, &QAction::triggered, this, [=]() { + this->onDrawPolygon(ShapeMode::mDiv); }); + connect(actDrawFreeDrawDiv, &QAction::triggered, this, [=]() { + this->onDrawFreeDraw(ShapeMode::mDiv); }); + + connect(actComform, SIGNAL(triggered()), this, SLOT(onDrawComform())); + connect(actCancel, SIGNAL(triggered()), this, SLOT(onDrawCancel())); + +} + +void DrawShapeView::ParamInit() +{ + m_pixmap_item = new QGraphicsPixmapItem(); + m_draw_path_item = new QGraphicsPathItem(); + m_draw_poly = new QPolygonF(); + v_hint_line = new QGraphicsLineItem(); + h_hint_line = new QGraphicsLineItem(); + m_pixmap_item->setZValue(0); + m_scene->addItem(m_pixmap_item); + m_scene->addItem(m_draw_path_item); + +} + +void DrawShapeView::onOpenImage() +{ + cur_image_name.clear(); + cur_image_name = QFileDialog::getOpenFileName(nullptr, + "Select an image", + "C:", + "Images (*.png *.jpeg *.jpg *.tiff *.bmp)" + ); + if (cur_image_name.length() == 0) + { + return; + } + if (m_cur_pixmap.load(cur_image_name)) + { + + FitShowImage(m_cur_pixmap); + } + this->repaint(); +} + +void DrawShapeView::onFitImageShow() +{ + FitShowImage(m_cur_pixmap); +} + +void DrawShapeView::onDrawLineShape() +{ + //draw_shape = EShapeType::sLine; + //view_mode = ViewMode::tDrawing; + //m_scene->addItem(v_hint_line); + //m_scene->addItem(h_hint_line); + cur_shape_item = new ShapeItemLine(); + shape_items.append(cur_shape_item); + m_scene->addItem(cur_shape_item); +} + +void DrawShapeView::onDrawRectangle1(ShapeMode mode) +{ + if (mode == ShapeMode::mAdd) + { + draw_shape = EShapeType::sRectangle1Add; + shape_mode = ShapeMode::mAdd; + } + else + { + draw_shape = EShapeType::sRectangle1Div; + shape_mode = ShapeMode::mDiv; + } + //view_mode = ViewMode::tDrawing; + //m_scene->addItem(v_hint_line); + //m_scene->addItem(h_hint_line); + cur_shape_item = new ShapeItemRect1(shape_mode); + shape_items.append(cur_shape_item); + m_scene->addItem(cur_shape_item); + +} + +void DrawShapeView::onDrawRectangle2(ShapeMode mode) +{ + if (mode == ShapeMode::mAdd) + { + draw_shape = EShapeType::sRectangle2Add; + shape_mode = ShapeMode::mAdd; + } + else + { + draw_shape = EShapeType::sRectangle2Div; + shape_mode = ShapeMode::mDiv; + } + cur_shape_item = new ShapeItemRect2(shape_mode); + shape_items.append(cur_shape_item); + m_scene->addItem(cur_shape_item); + //view_mode = ViewMode::tDrawing; + //m_scene->addItem(v_hint_line); + //m_scene->addItem(h_hint_line); +} + +void DrawShapeView::onDrawPolygon(ShapeMode mode) +{ + if (mode == ShapeMode::mAdd) + { + draw_shape = EShapeType::sPolygonAdd; + shape_mode = ShapeMode::mAdd; + } + else + { + draw_shape = EShapeType::sPolygonDiv; + shape_mode = ShapeMode::mDiv; + } + view_mode = ViewMode::tDrawing; + m_scene->addItem(v_hint_line); + m_scene->addItem(h_hint_line); +} + +void DrawShapeView::onDrawFreeDraw(ShapeMode mode) +{ +} + +void DrawShapeView::onDrawComform() +{ + shape_data.shapePolygon.clear(); + shape_data.shapeMode.clear(); + for (auto& elem : shape_items) + { + shape_data.shapePolygon.append(elem->GetShapePoygonF()); + shape_data.shapeMode.append(elem->GetShapeMode()); + } + emit RegionComform(shape_data); + this->hide(); +} + +void DrawShapeView::onDrawCancel() +{ +} + +void DrawShapeView::drawFinished() +{ + m_scene->removeItem(v_hint_line); + m_scene->removeItem(h_hint_line); + if (m_draw_poly->count() > 1) + { + //m_draw_poly->last() = m_draw_poly->first(); + //shape_data.shapePolygon.append(*m_draw_poly); + //shape_data.shapeType.append(draw_shape); + //shape_data.shapeMode.append(shape_mode); + //tmpPath.clear(); + //tmpPath.addPolygon(*m_draw_poly); + //m_draw_path_item->setPath(tmpPath); + if (draw_shape == EShapeType::sPolygonAdd || draw_shape == EShapeType::sPolygonDiv) + { + cur_shape_item = new ShapeItemPolygon(*m_draw_poly, shape_mode, QPointF(0, 0)); + m_scene->addItem(cur_shape_item); + shape_items.append(cur_shape_item); + } + m_draw_poly->clear(); + m_draw_path_item->setPath(QPainterPath()); + } + view_mode = ViewMode::tNone; + shape_mode = ShapeMode::mNone; + draw_shape = EShapeType::sNone; +} + +void DrawShapeView::drawHintInfo(QPainter* painter) +{ + m_hint_str.clear(); + m_hint_str = QString("mouse:%1,%2").arg(m_cur_pos_view.x()).arg(m_cur_pos_view.y()); + m_hint_str.append(QString(" - mouse scene:%1,%2").arg(m_cur_pos_scene.x()).arg(m_cur_pos_scene.y())); + painter->setPen(Qt::white); + painter->setBrush(Qt::NoBrush); + painter->drawText(10, 25, m_hint_str); +} + +void DrawShapeView::drawCurrentShape(QPainter* painter) +{ + if (view_mode != ViewMode::tDrawing) + { + return; + } + //painter->setPen(Qt::NoPen); + //painter->setBrush(m_hint_bg_color); + //for (auto tmp_polygon : shape_data.shapePolygon) { + // QPainterPath tmp_view_path; + // QPolygonF tmp_polygon_view; + // for (auto _point : tmp_polygon) + // { + // tmp_polygon_view.append(mapFromScene(_point)); + // } + // if (!tmp_polygon_view.isEmpty()) + // { + // tmp_view_path.addPolygon(tmp_polygon_view); + // painter->drawPath(tmp_view_path); + // } + //} +} + +void DrawShapeView::mousePressEvent(QMouseEvent* event) +{ + QGraphicsView::mousePressEvent(event); + m_lastMousePos = mapToScene(event->pos()); + if (event->button() == Qt::RightButton) + { + if (view_mode != ViewMode::tDrawing) + { + m_menu->exec(mapToGlobal(event->pos())); + } + else + { + drawFinished(); + } + } + else if (event->button() == Qt::LeftButton) + { + if (view_mode == ViewMode::tDrawing) + { + if (m_draw_poly->count() == 0) + { + m_draw_poly->append(m_lastMousePos); + } + m_draw_poly->append(m_lastMousePos); + } + switch (draw_shape) + { + case EShapeType::sLine: + case EShapeType::sRectangle1Add: + case EShapeType::sRectangle1Div: + if (m_draw_poly->count() == 3) + { + drawFinished(); + } + break; + case EShapeType::sRectangle2Add: + case EShapeType::sRectangle2Div: + if (m_draw_poly->count() == 4) + { + drawFinished(); + } + break; + default: + break; + } + } + else if (event->button() == Qt::MiddleButton) + { + + view_mode = ViewMode::tTranslate; + + } +} + +void DrawShapeView::mouseReleaseEvent(QMouseEvent* event) +{ + QGraphicsView::mouseReleaseEvent(event); + if (event->button() == Qt::RightButton) + { + } + else if (event->button() == Qt::LeftButton) + { + } + else if (event->button() == Qt::MiddleButton) + { + if (view_mode != ViewMode::tDrawing) + { + view_mode = ViewMode::tNone; + } + } +} + +void DrawShapeView::mouseMoveEvent(QMouseEvent* event) +{ + QGraphicsView::mouseMoveEvent(event); + m_cur_pos_view = event->pos(); + m_cur_pos_scene = mapToScene(m_cur_pos_view); + + if (view_mode == ViewMode::tTranslate) + { + m_centerPos.setX(m_centerPos.x() - m_cur_pos_scene.x() + m_lastMousePos.x()); + m_centerPos.setY(m_centerPos.y() - m_cur_pos_scene.y() + m_lastMousePos.y()); + centerOn(m_centerPos); + } + if (view_mode == ViewMode::tDrawing) + { + v_hint_line->setLine( + m_cur_pos_scene.x(), + m_cur_pos_scene.y() - 1000, + m_cur_pos_scene.x(), + m_cur_pos_scene.y() + 1000); + h_hint_line->setLine( + m_cur_pos_scene.x() - 1000, + m_cur_pos_scene.y(), + m_cur_pos_scene.x() + 1000, + m_cur_pos_scene.y()); + if (m_draw_poly->count() > 1) + { + m_draw_poly->last() = m_cur_pos_scene; + tmpPath.clear(); + tmpPath.addPolygon(*m_draw_poly); + m_draw_path_item->setPath(tmpPath); + } + } + this->viewport()->repaint(); +} + +void DrawShapeView::wheelEvent(QWheelEvent* event) +{ + setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + qreal scaleFactor = 1.0; + if (event->delta() > 0) + { + scaleFactor = 1.2; + } + else + { + scaleFactor = 1 / 1.2; + } + qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width(); + if (factor < 0.01 || factor > 2000) + return; + scale(scaleFactor, scaleFactor); +} + +void DrawShapeView::paintEvent(QPaintEvent* event) +{ + QGraphicsView::paintEvent(event); + QPainter painter(this->viewport()); + drawCurrentShape(&painter); + drawHintInfo(&painter); +} + +void DrawShapeView::drawBackground(QPainter* painter, const QRectF& rect) +{ + QGraphicsView::drawBackground(painter, rect); + painter->setBrush(m_bg_color); + painter->setPen(Qt::NoPen); + painter->drawRect(rect); + painter->setBrush(Qt::NoBrush); + painter->setPen(m_grid_pen); + painter->drawLine(-3000, 0, 3000, 0); + painter->drawLine(0, -3000, 0, 3000); +} + diff --git a/ShapeDrawer/DrawShapeView.hpp b/ShapeDrawer/DrawShapeView.hpp new file mode 100644 index 0000000..b143c29 --- /dev/null +++ b/ShapeDrawer/DrawShapeView.hpp @@ -0,0 +1,124 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "DrawViewParams.h" +#include "ShapeItemLine.h" +#include "ShapeItemRect1.h" +#include "ShapeItemRect2.h" +#include "ShapeItemPolygon.h" +#include "halconcpp/HalconCpp.h" + +static std::mutex draw_view_lock; +class DrawShapeView :public QGraphicsView +{ + Q_OBJECT +public: + + explicit DrawShapeView(QWidget* parent = Q_NULLPTR); + + ~DrawShapeView() {}; + + static DrawShapeView* instance; + + static DrawShapeView* getInst(); + //DrawShapeView(const DrawShapeView&); + DrawShapeView& operator=(const DrawShapeView&); +private: + class Deletor + { + public: + ~Deletor() + { + if (DrawShapeView::instance != nullptr) + { + delete DrawShapeView::instance; + } + } + }; + static Deletor deletor; +private: + QGraphicsScene* m_scene; + QColor m_bg_color = QColor(34, 34, 34, 255); + QColor m_grid_color = QColor(10, 10, 10, 255); + QColor m_hint_bg_color = QColor(0, 255, 0, 100); + QPen m_grid_pen = QPen(m_grid_color, 3); + QString m_hint_str; + QPoint m_cur_pos_view; + QPointF m_cur_pos_scene; + QPointF m_hint_tl; + + ViewMode view_mode = ViewMode::tNone; + EShapeType draw_shape; + ShapeMode shape_mode; + ShapeDataStruct shape_data; + QList shape_items; + ShapeItemBase* cur_shape_item = nullptr; + + QString cur_image_name; + QPixmap m_cur_pixmap; + + QPointF m_lastMousePos; // 鼠标最后按下的位置 + QPointF m_centerPos; // + qreal m_scale = 1.0; // 缩放值 + + QTransform m_transform; + + QMenu* m_menu; + QGraphicsPixmapItem* m_pixmap_item; + QGraphicsPathItem* m_draw_path_item; + QPolygonF* m_draw_poly; + QPainterPath tmpPath; + + QGraphicsLineItem* v_hint_line; + QGraphicsLineItem* h_hint_line; + +public: + void FitShowImage(const QPixmap& pixmap); + void FitShowImage(const QPixmap& pixmap, ShapeDataStruct& shape_data); + static HalconCpp::HRegion GetHRegionFromData(const ShapeDataStruct& shape_data); +private: + void MenuInit(); + void ParamInit(); +public slots: + void onOpenImage(); + void onFitImageShow(); + void onDrawLineShape(); + void onDrawRectangle1(ShapeMode mode); + void onDrawRectangle2(ShapeMode mode); + void onDrawPolygon(ShapeMode mode); + void onDrawFreeDraw(ShapeMode mode); + void onDrawComform(); + void onDrawCancel(); +signals: + void DrawFinished(); + void RegionComform(ShapeDataStruct shape_data); +private: + void drawFinished(); + void drawHintInfo(QPainter* painter); + void drawCurrentShape(QPainter* painter); +protected: + void mousePressEvent(QMouseEvent* event) override; + + void mouseReleaseEvent(QMouseEvent* event) override; + + void mouseMoveEvent(QMouseEvent* event) override; + + void wheelEvent(QWheelEvent* event) override; + + void paintEvent(QPaintEvent* event) override; + + void drawBackground(QPainter* painter, const QRectF& rect) override; + +}; + diff --git a/ShapeDrawer/DrawViewParams.h b/ShapeDrawer/DrawViewParams.h new file mode 100644 index 0000000..ce937f1 --- /dev/null +++ b/ShapeDrawer/DrawViewParams.h @@ -0,0 +1,41 @@ +#pragma once +#include + +enum class ViewMode +{ + tNone, + tTranslate, + tZoom, + tDrawing +}; + +enum class EShapeType +{ + sNone, + sLine, + sRectangle1Add, + sRectangle2Add, + sPolygonAdd, + sFreeDrawAdd, + sRectangle1Div, + sRectangle2Div, + sPolygonDiv, + sFreeDrawDiv +}; + +enum class ShapeMode +{ + mNone, + mAdd, + mDiv +}; + +struct ShapeDataStruct +{ + QString name; + QVector shapePolygon; + QVector shapeType; + QVector shapeMode; +}; + + diff --git a/ShapeDrawer/ShapeControlItem.cpp b/ShapeDrawer/ShapeControlItem.cpp new file mode 100644 index 0000000..d2be738 --- /dev/null +++ b/ShapeDrawer/ShapeControlItem.cpp @@ -0,0 +1,78 @@ +#include "ShapeControlItem.h" +#include "ShapeItemBase.h" + +//构造函数 +ShapeControlItem::ShapeControlItem(QGraphicsItemGroup* parent, + ControlItemType type, + QPointF p, int style) + : QAbstractGraphicsShapeItem(parent) +{ + setPos(p); + setAcceptHoverEvents(true); + handle_type = type; + this->setFlags(QGraphicsItem::ItemIsSelectable | + QGraphicsItem::ItemIsMovable | + QGraphicsItem::ItemIsFocusable); + + bounding_rect = QRectF(-handle_size * 0.5, -handle_size * 0.5, + handle_size, handle_size); +} + +void ShapeControlItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + active_states = true; + Q_UNUSED(event); +} + +void ShapeControlItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + active_states = false; + Q_UNUSED(event); +} + +//拖拽 鼠标感应区域 +QRectF ShapeControlItem::boundingRect() const +{ + return bounding_rect; //拖拽 鼠标感应区域 +} +//位置重绘 +void ShapeControlItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + painter->setPen(Qt::NoPen); + if (active_states) + { + painter->setBrush(handle_active_color); + bounding_rect = QRectF(-handle_active_size * 0.5, -handle_active_size * 0.5, + handle_active_size, handle_active_size); + } + else + { + painter->setBrush(handle_color); + bounding_rect = QRectF(-handle_size * 0.5, -handle_size * 0.5, + handle_size, handle_size); + } + painter->drawRoundedRect(bounding_rect, 2, 2); +} +//鼠标事件处理 +void ShapeControlItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + if (event->buttons() == Qt::LeftButton) + { + last_point = cur_point; + cur_point = this->mapToParent(event->pos()); + dx = cur_point.x() - last_point.x(); + dy = cur_point.y() - last_point.y(); + if (this->handle_type != cCenter && this->handle_type != cRotate) + { + //结果正常、更新位置 + this->setPos(cur_point); + } + } + emit PositionChanged(); +} + +void ShapeControlItem::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + + QGraphicsItem::mousePressEvent(event); +} diff --git a/ShapeDrawer/ShapeControlItem.h b/ShapeDrawer/ShapeControlItem.h new file mode 100644 index 0000000..da383a6 --- /dev/null +++ b/ShapeDrawer/ShapeControlItem.h @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "DrawViewParams.h" + +enum ControlItemType +{ + cNone, + cCenter, + cEdgeNode, + cRotate +}; + +class ShapeControlItem : + public QObject + , public QAbstractGraphicsShapeItem +{ + Q_OBJECT +public: + explicit ShapeControlItem(QGraphicsItemGroup* parent, + ControlItemType type, + QPointF p, int style = 1); + ControlItemType getType() { return handle_type; } + qreal getDx() { return dx; } + qreal getDy() { return dy; } + QPointF getCenterPointF() { return this->pos(); } +private: + QRectF bounding_rect; + int handle_size = 5; + int handle_active_size = 8; + bool active_states = false; + QColor handle_color = QColor(255, 255, 0, 100); + QColor handle_active_color = QColor(255, 0, 0, 100); + qreal dx; + qreal dy; + QPointF last_point; + QPointF cur_point; + ControlItemType handle_type; +signals: + void PositionChanged(); +protected: + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; + virtual QRectF boundingRect() const override; + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + +}; + diff --git a/ShapeDrawer/ShapeItemBase.cpp b/ShapeDrawer/ShapeItemBase.cpp new file mode 100644 index 0000000..547b602 --- /dev/null +++ b/ShapeDrawer/ShapeItemBase.cpp @@ -0,0 +1,77 @@ +#include "ShapeItemBase.h" + + +ShapeItemBase::ShapeItemBase(EShapeType type) :types(type) +{ + + setHandlesChildEvents(false);//设置后才能将事件传递到子元素 + if (type != EShapeType::sNone) //模式选择 自定义模式用于显示亚像素轮廓和Region 不设定任何属性 + { + this->setFlags(QGraphicsItem::ItemIsSelectable | + QGraphicsItem::ItemIsMovable | + QGraphicsItem::ItemIsFocusable); + } + this->setCursor(Qt::ArrowCursor); + activeHandle = nullptr; + bounding_rect = QRectF(0, 0, 100, 100); + ItemPath.addRect(bounding_rect); + ItemShape.addRect(bounding_rect); +} + +ShapeItemBase::~ShapeItemBase() +{ + +} + +void ShapeItemBase::focusInEvent(QFocusEvent* event) +{ + Q_UNUSED(event); + this->setZValue(99); + shape_active = true; +} + +void ShapeItemBase::focusOutEvent(QFocusEvent* event) +{ + Q_UNUSED(event); + this->setZValue(1); + shape_active = false; +} + +QRectF ShapeItemBase::boundingRect() const +{ + return ItemPath.boundingRect().united(ItemShape.boundingRect()); +} + +QPainterPath ShapeItemBase::shape() const +{ + return ItemShape.united(ItemPath); +} + +void ShapeItemBase::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + QGraphicsItemGroup::paint(painter, option, widget); + painter->setPen(Qt::NoPen); + if (shape_active) + { + painter->setBrush(activeBackgroundColor); + //painter->drawRect(this->boundingRect()); + } + else + { + if (m_shape_mode == ShapeMode::mAdd) + { + painter->setBrush(addBackgroundColor); + } + else + { + painter->setBrush(divBackgroundColor); + } + } + if (!ItemPath.isEmpty()) + { + painter->setPen(ItemColor); + //painter->setBrush(Qt::NoBrush); + painter->drawPath(ItemPath); + } +} + diff --git a/ShapeDrawer/ShapeItemBase.h b/ShapeDrawer/ShapeItemBase.h new file mode 100644 index 0000000..9c9a77c --- /dev/null +++ b/ShapeDrawer/ShapeItemBase.h @@ -0,0 +1,66 @@ +#pragma once +#include +#include +#include "DrawViewParams.h" +#include "ShapeControlItem.h" + +class ShapeItemBase : + public QObject, public QGraphicsItemGroup +{ + Q_OBJECT +public: + ShapeItemBase(EShapeType type); + virtual ~ShapeItemBase(); + void SetZoomVal(qreal ZoomVal) { scaler = ZoomVal; } + qreal GetContrSize() const { return ContrSize; } + QPolygonF GetShapePoygonF() + { + //if (!itemPolygon.isClosed()) + //{ + // itemPolygon.append(itemPolygon.first()); + //} + for (int i = 0; i < itemPolygon.count(); i++) + { + itemPolygon[i] = mapToScene(itemPolygon[i]); + } + return itemPolygon; + } + ShapeMode GetShapeMode() { return m_shape_mode; } +private: + virtual void shapeInit() = 0; +public slots: + virtual void calculateShape() = 0; + virtual void rotateShape(qreal delta) {}; +protected: + void focusInEvent(QFocusEvent* event) override; + + void focusOutEvent(QFocusEvent* event) override; + + QRectF boundingRect() const override; + + QPainterPath shape() const override; + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + +protected: + ShapeControlItem* activeHandle; + QList ControlList; + QRectF bounding_rect; + qreal scaler; //缩放系数 + const qreal ContrSize = 8; //控制点尺寸 + EShapeType types; //枚举类型 + QColor ItemColor = QColor(0, 100, 200); //线条颜色 + QColor BackgroundColor = QColor(0, 160, 230, 100); //填充背景颜色 + QColor addBackgroundColor = QColor(0, 255, 0, 100); //填充背景颜色 + QColor divBackgroundColor = QColor(255, 0, 0, 50); //填充背景颜色 + QColor activeBackgroundColor = QColor(255, 255, 0, 100); //填充背景颜色 + bool shape_active = false; + QString ItemDiscrib = QString::fromLocal8Bit("描述"); + + ShapeMode m_shape_mode = ShapeMode::mNone; + QPointF Center; //中心点 + QPainterPath ItemShape; //有边框区域 + QPainterPath ItemPath; //有边框区域 + QPolygonF itemPolygon; +}; + diff --git a/ShapeDrawer/ShapeItemLine.cpp b/ShapeDrawer/ShapeItemLine.cpp new file mode 100644 index 0000000..6a5b24a --- /dev/null +++ b/ShapeDrawer/ShapeItemLine.cpp @@ -0,0 +1,67 @@ +#include "ShapeItemLine.h" + +ShapeItemLine::ShapeItemLine(QPointF pos) : + ShapeItemBase(EShapeType::sLine) +{ + this->setPos(pos); + shapeInit(); +} + +ShapeItemLine::~ShapeItemLine() +{ +} + +void ShapeItemLine::calculateShape() +{ + if (this->sender() == nullptr) + { + ControlList[1]->setX(0.5 * (ControlList[0]->x() + ControlList[2]->x())); + ControlList[1]->setY(0.5 * (ControlList[0]->y() + ControlList[2]->y())); + ItemPath.clear(); + itemPolygon[0] = ControlList[0]->getCenterPointF(); + itemPolygon[1] = ControlList[2]->getCenterPointF(); + ItemPath.addPolygon(itemPolygon); + ItemPath.closeSubpath(); + ItemShape.clear(); + ItemShape.addRect(ItemPath.boundingRect()); + } + else + { + activeHandle = dynamic_cast(this->sender()); + + if (activeHandle->getType() == cCenter) + { + //this->moveBy(activeHandle->getDx(), activeHandle->getDy()); + } + else if (activeHandle->getType() == cRotate) + { + + } + else { + + ControlList[1]->setX(0.5 * (ControlList[0]->x() + ControlList[2]->x())); + ControlList[1]->setY(0.5 * (ControlList[0]->y() + ControlList[2]->y())); + ItemPath.clear(); + itemPolygon[0] = ControlList[0]->getCenterPointF(); + itemPolygon[1] = ControlList[2]->getCenterPointF(); + ItemPath.addPolygon(itemPolygon); + ItemPath.closeSubpath(); + ItemShape.clear(); + ItemShape.addRect(ItemPath.boundingRect()); + } + } +} + +void ShapeItemLine::shapeInit() +{ + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(10, 10))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cCenter, QPointF(50, 50))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(90, 90))); + itemPolygon.append(QPointF(ControlList[0]->x(), ControlList[0]->y())); + itemPolygon.append(QPointF(ControlList[2]->x(), ControlList[2]->y())); + for (auto elem : ControlList) + { + connect(elem, SIGNAL(PositionChanged()), this, SLOT(calculateShape())); + } + calculateShape(); +} diff --git a/ShapeDrawer/ShapeItemLine.h b/ShapeDrawer/ShapeItemLine.h new file mode 100644 index 0000000..3612b58 --- /dev/null +++ b/ShapeDrawer/ShapeItemLine.h @@ -0,0 +1,15 @@ +#include "ShapeItemBase.h" + +class ShapeItemLine + :public ShapeItemBase +{ +public: + ShapeItemLine(QPointF pos = QPointF(0, 0)); + + ~ShapeItemLine(); +private: + void calculateShape() override; + + void shapeInit() override; +}; + diff --git a/ShapeDrawer/ShapeItemPolygon.cpp b/ShapeDrawer/ShapeItemPolygon.cpp new file mode 100644 index 0000000..454c96e --- /dev/null +++ b/ShapeDrawer/ShapeItemPolygon.cpp @@ -0,0 +1,66 @@ +#include "ShapeItemPolygon.h" + +ShapeItemPolygon::ShapeItemPolygon(QPolygonF polygon, ShapeMode shape_mode, QPointF pos) : + ShapeItemBase(EShapeType::sLine) +{ + m_shape_mode = shape_mode; + itemPolygon = polygon; + this->setPos(pos); + shapeInit(); +} + +ShapeItemPolygon::~ShapeItemPolygon() +{ +} + +void ShapeItemPolygon::calculateShape() +{ + if (this->sender() == nullptr) + { + sizeChanged(); + } + else + { + activeHandle = dynamic_cast(this->sender()); + + if (activeHandle->getType() == cCenter) + { + //this->moveBy(activeHandle->getDx(), activeHandle->getDy()); + } + else if (activeHandle->getType() == cRotate) + { + + } + else { + sizeChanged(); + } + } +} + +void ShapeItemPolygon::sizeChanged() +{ + ItemPath.clear(); + int i = 0; + for (auto elem : ControlList) + { + itemPolygon[i] = elem->getCenterPointF(); + i++; + } + ItemPath.addPolygon(itemPolygon); + ItemPath.closeSubpath(); + ItemShape.clear(); + ItemShape.addRect(ItemPath.boundingRect()); +} + +void ShapeItemPolygon::shapeInit() +{ + for (auto elem : itemPolygon) + { + ControlList.append(new ShapeControlItem(this, cEdgeNode, elem)); + } + for (auto elem : ControlList) + { + connect(elem, SIGNAL(PositionChanged()), this, SLOT(calculateShape())); + } + calculateShape(); +} diff --git a/ShapeDrawer/ShapeItemPolygon.h b/ShapeDrawer/ShapeItemPolygon.h new file mode 100644 index 0000000..76b918c --- /dev/null +++ b/ShapeDrawer/ShapeItemPolygon.h @@ -0,0 +1,15 @@ +#include "ShapeItemBase.h" + +class ShapeItemPolygon + :public ShapeItemBase +{ +public: + ShapeItemPolygon(QPolygonF polygon, ShapeMode shape_mode, QPointF pos = QPointF(0, 0)); + + ~ShapeItemPolygon(); +private: + void calculateShape() override; + void sizeChanged(); + void shapeInit() override; +}; + diff --git a/ShapeDrawer/ShapeItemRect1.cpp b/ShapeDrawer/ShapeItemRect1.cpp new file mode 100644 index 0000000..1f92903 --- /dev/null +++ b/ShapeDrawer/ShapeItemRect1.cpp @@ -0,0 +1,84 @@ +#include "ShapeItemRect1.h" + +ShapeItemRect1::ShapeItemRect1(ShapeMode shape_mode) : + ShapeItemBase(EShapeType::sRectangle1Add) +{ + m_shape_mode = shape_mode; + shapeInit(); + +} + +ShapeItemRect1::~ShapeItemRect1() +{ +} + +void ShapeItemRect1::calculateShape() +{ + if (this->sender() == nullptr) + { + sizeChanged(); + } + else + { + activeHandle = dynamic_cast(this->sender()); + + if (activeHandle->getType() == cCenter) + { + //this->moveBy(activeHandle->getDx(), activeHandle->getDy()); + } + else if (activeHandle->getType() == cRotate) + { + + } + else { + + sizeChanged(); + } + } +} + +void ShapeItemRect1::shapeInit() +{ + ControlList.append(new ShapeControlItem(this, ControlItemType::cCenter, QPointF(50, 50))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(10, 10))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(90, 10))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(90, 90))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(10, 90))); + itemPolygon.append(QPointF(ControlList[1]->x(), ControlList[1]->y())); + itemPolygon.append(QPointF(ControlList[2]->x(), ControlList[2]->y())); + itemPolygon.append(QPointF(ControlList[3]->x(), ControlList[3]->y())); + itemPolygon.append(QPointF(ControlList[4]->x(), ControlList[4]->y())); + ControlList[2]->setVisible(false); + ControlList[4]->setVisible(false); + for (auto elem : ControlList) + { + connect(elem, SIGNAL(PositionChanged()), this, SLOT(calculateShape())); + } + calculateShape(); +} + +void ShapeItemRect1::sizeChanged() +{ + activeHandle = dynamic_cast(this->sender()); + + ControlList[0]->setX(0.5 * (ControlList[1]->x() + ControlList[3]->x())); + ControlList[0]->setY(0.5 * (ControlList[1]->y() + ControlList[3]->y())); + + ControlList[2]->setX(ControlList[3]->x()); + ControlList[2]->setY(ControlList[1]->y()); + + ControlList[4]->setX(ControlList[1]->x()); + ControlList[4]->setY(ControlList[3]->y()); + + itemPolygon[0] = ControlList[1]->getCenterPointF(); + itemPolygon[1] = ControlList[2]->getCenterPointF(); + itemPolygon[2] = ControlList[3]->getCenterPointF(); + itemPolygon[3] = ControlList[4]->getCenterPointF(); + + ItemPath.clear(); + ItemPath.addPolygon(itemPolygon); + ItemPath.closeSubpath(); + ItemShape.clear(); + ItemShape.addRect(ItemPath.boundingRect()); +} + diff --git a/ShapeDrawer/ShapeItemRect1.h b/ShapeDrawer/ShapeItemRect1.h new file mode 100644 index 0000000..c627bbb --- /dev/null +++ b/ShapeDrawer/ShapeItemRect1.h @@ -0,0 +1,17 @@ +#include "ShapeItemBase.h" + +class ShapeItemRect1 + :public ShapeItemBase +{ +public: + ShapeItemRect1(ShapeMode shape_mode); + + ~ShapeItemRect1(); +private: + void calculateShape() override; + + void shapeInit() override; + + void sizeChanged(); +}; + diff --git a/ShapeDrawer/ShapeItemRect2.cpp b/ShapeDrawer/ShapeItemRect2.cpp new file mode 100644 index 0000000..74c305a --- /dev/null +++ b/ShapeDrawer/ShapeItemRect2.cpp @@ -0,0 +1,103 @@ +#include "ShapeItemRect2.h" + +ShapeItemRect2::ShapeItemRect2(ShapeMode shape_mode, QPointF pos) : + ShapeItemBase(EShapeType::sRectangle2Add) +{ + m_shape_mode = shape_mode; + shapeInit(); +} + +ShapeItemRect2::~ShapeItemRect2() +{ +} + +void ShapeItemRect2::calculateShape() +{ + if (this->sender() == nullptr) + { + sizeChanged(); + } + else + { + activeHandle = dynamic_cast(this->sender()); + + if (activeHandle->getType() == cCenter) + { + //this->moveBy(activeHandle->getDx(), activeHandle->getDy()); + } + else if (activeHandle->getType() == cRotate) + { + rotateShape(activeHandle->getDy()); + } + else { + + sizeChanged(); + } + } +} + +void ShapeItemRect2::rotateShape(qreal delta) +{ + m_transform = this->transform(); + m_transform.rotate(1); + qreal delta_angle = 1.0; + if (delta > 0) + { + delta_angle = 1.0; + } + else + { + delta_angle = -1.0; + } + m_transform = m_transform.rotate(delta_angle); + this->setTransform(m_transform); +} + +void ShapeItemRect2::shapeInit() +{ + ControlList.append(new ShapeControlItem(this, ControlItemType::cCenter, QPointF(50, 50))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(10, 10))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(90, 10))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(90, 90))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cEdgeNode, QPointF(10, 90))); + ControlList.append(new ShapeControlItem(this, ControlItemType::cRotate, QPointF(50, 10))); + itemPolygon.append(QPointF(ControlList[1]->x(), ControlList[1]->y())); + itemPolygon.append(QPointF(ControlList[2]->x(), ControlList[2]->y())); + itemPolygon.append(QPointF(ControlList[3]->x(), ControlList[3]->y())); + itemPolygon.append(QPointF(ControlList[4]->x(), ControlList[4]->y())); + ControlList[2]->setVisible(false); + ControlList[4]->setVisible(false); + for (auto elem : ControlList) + { + connect(elem, SIGNAL(PositionChanged()), this, SLOT(calculateShape())); + } + calculateShape(); +} + +void ShapeItemRect2::sizeChanged() +{ + activeHandle = dynamic_cast(this->sender()); + + ControlList[0]->setX(0.5 * (ControlList[1]->x() + ControlList[3]->x())); + ControlList[0]->setY(0.5 * (ControlList[1]->y() + ControlList[3]->y())); + ControlList[5]->setX(ControlList[0]->x()); + ControlList[5]->setY(ControlList[1]->y()); + + ControlList[2]->setX(ControlList[3]->x()); + ControlList[2]->setY(ControlList[1]->y()); + + ControlList[4]->setX(ControlList[1]->x()); + ControlList[4]->setY(ControlList[3]->y()); + + itemPolygon[0] = ControlList[1]->getCenterPointF(); + itemPolygon[1] = ControlList[2]->getCenterPointF(); + itemPolygon[2] = ControlList[3]->getCenterPointF(); + itemPolygon[3] = ControlList[4]->getCenterPointF(); + + ItemPath.clear(); + ItemPath.addPolygon(itemPolygon); + ItemPath.closeSubpath(); + ItemShape.clear(); + ItemShape.addRect(ItemPath.boundingRect()); +} + diff --git a/ShapeDrawer/ShapeItemRect2.h b/ShapeDrawer/ShapeItemRect2.h new file mode 100644 index 0000000..da81229 --- /dev/null +++ b/ShapeDrawer/ShapeItemRect2.h @@ -0,0 +1,21 @@ +#include "ShapeItemBase.h" + +class ShapeItemRect2 + :public ShapeItemBase +{ +public: + ShapeItemRect2(ShapeMode shape_mode, QPointF pos = QPointF(0, 0)); + + ~ShapeItemRect2(); +private: + QTransform m_transform; +private: + void calculateShape() override; + + void rotateShape(qreal delta) override; + + void shapeInit() override; + + void sizeChanged(); +}; +