Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qgroundcontrol
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Valentin Platzgummer
qgroundcontrol
Commits
6fd60bb5
Commit
6fd60bb5
authored
Sep 24, 2020
by
Valentin Platzgummer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CircularSurvey concurr. update improved
parent
81a0bb81
Changes
12
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
628 additions
and
487 deletions
+628
-487
search_limit.pb.h
...buntu/include/ortools/constraint_solver/search_limit.pb.h
+2
-0
qgroundcontrol.pro
qgroundcontrol.pro
+2
-0
CircularSurveyItemEditor.qml
src/PlanView/CircularSurveyItemEditor.qml
+40
-85
CSWorker.cpp
src/Wima/CSWorker.cpp
+368
-0
CSWorker.h
src/Wima/CSWorker.h
+57
-0
CircularSurvey.cc
src/Wima/CircularSurvey.cc
+80
-340
CircularSurvey.h
src/Wima/CircularSurvey.h
+13
-19
snake.cpp
src/Wima/Snake/snake.cpp
+32
-13
snake.h
src/Wima/Snake/snake.h
+6
-1
WimaPlaner.cc
src/Wima/WimaPlaner.cc
+7
-6
WimaPlaner.h
src/Wima/WimaPlaner.h
+21
-21
CircularSurveyMapVisual.qml
src/WimaView/CircularSurveyMapVisual.qml
+0
-2
No files found.
libs/or-tools-src-ubuntu/include/ortools/constraint_solver/search_limit.pb.h
View file @
6fd60bb5
// Generated by the protocol buffer compiler. DO NOT EDIT!
this
->
_calculating
=
false
;
emit
calculatingChanged
();
// source: ortools/constraint_solver/search_limit.proto
#ifndef GOOGLE_PROTOBUF_INCLUDED_ortools_2fconstraint_5fsolver_2fsearch_5flimit_2eproto
...
...
qgroundcontrol.pro
View file @
6fd60bb5
...
...
@@ -413,6 +413,7 @@ FORMS += \
#
HEADERS
+=
\
src
/
Wima
/
CSWorker
.
h
\
src
/
Wima
/
CircularSurvey
.
h
\
src
/
Wima
/
Geometry
/
GenericCircle
.
h
\
src
/
Wima
/
Snake
/
clipper
/
clipper
.
hpp
\
...
...
@@ -502,6 +503,7 @@ HEADERS += \
src
/
comm
/
ros_bridge
/
include
/
topic_subscriber
.
h
\
src
/
comm
/
utilities
.
h
SOURCES
+=
\
src
/
Wima
/
CSWorker
.
cpp
\
src
/
Wima
/
CircularSurvey
.
cc
\
src
/
Wima
/
Snake
/
clipper
/
clipper
.
cpp
\
src
/
Wima
/
Snake
/
snake
.
cpp
\
...
...
src/PlanView/CircularSurveyItemEditor.qml
View file @
6fd60bb5
...
...
@@ -92,9 +92,6 @@ Rectangle {
columns
:
2
visible
:
transectsHeader
.
checked
QGCLabel
{
text
:
qsTr
(
"
Delta R
"
)
}
FactTextField
{
fact
:
missionItem
.
deltaR
...
...
@@ -131,12 +128,26 @@ Rectangle {
}
}
Column
{
GridLayout
{
anchors.left
:
parent
.
left
anchors.right
:
parent
.
right
spacing
:
_margin
columnSpacing
:
_margin
rowSpacing
:
_margin
columns
:
2
visible
:
transectsHeader
.
checked
QGCButton
{
text
:
qsTr
(
"
Reverse
"
)
onClicked
:
missionItem
.
reverse
();
Layout.fillWidth
:
true
}
QGCButton
{
text
:
qsTr
(
"
Reset Ref.
"
)
onClicked
:
missionItem
.
resetReference
();
Layout.fillWidth
:
true
}
QGCCheckBox
{
id
:
relAlt
text
:
qsTr
(
"
Relative altitude
"
)
...
...
@@ -144,6 +155,8 @@ Rectangle {
enabled
:
missionItem
.
cameraCalc
.
isManualCamera
&&
!
missionItem
.
followTerrain
visible
:
QGroundControl
.
corePlugin
.
options
.
showMissionAbsoluteAltitude
||
(
!
missionItem
.
cameraCalc
.
distanceToSurfaceRelative
&&
!
missionItem
.
followTerrain
)
onClicked
:
missionItem
.
cameraCalc
.
distanceToSurfaceRelative
=
checked
Layout.fillWidth
:
true
Layout.columnSpan
:
2
Connections
{
target
:
missionItem
.
cameraCalc
...
...
@@ -151,96 +164,38 @@ Rectangle {
}
}
FactCheckBox
{
text
:
qsTr
(
"
Reverse Path
"
)
fact
:
missionItem
.
reverse
}
QGCButton
{
text
:
qsTr
(
"
Reset Reference
"
)
onClicked
:
missionItem
.
resetReference
();
Layout.fillWidth
:
true
}
}
SectionHeader
{
id
:
miscellaneousHeader
text
:
qsTr
(
"
Miscellaneous
"
)
}
ColumnLayout
{
Column
{
anchors.left
:
parent
.
left
anchors.right
:
parent
.
right
spacing
:
_margin
visible
:
miscellaneousHeader
.
checked
GridLayout
{
Layout.fillWidth
:
true
columnSpacing
:
_margin
rowSpacing
:
_margin
columns
:
2
QGCLabel
{
text
:
qsTr
(
"
Max Waypoints
"
)
}
FactTextField
{
fact
:
missionItem
.
maxWaypoints
Layout.fillWidth
:
true
BusyIndicator
{
id
:
indicator
anchors.horizontalCenter
:
parent
.
horizontalCenter
running
:
missionItem
.
calculating
onRunningChanged
:
{
if
(
running
){
visible
=
true
}
else
{
timer
.
restart
()
}
}
}
// GridLayout
}
// ColumnLayout
/*
// The following code causes seg. faults from time to time
SectionHeader {
id: terrainHeader
text: qsTr("Terrain")
checked: missionItem.followTerrain
}
Timer
{
id
:
timer
interval
:
2000
repeat
:
false
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
spacing: _margin
visible: terrainHeader.checked
QGCCheckBox {
id: followsTerrainCheckBox
text: qsTr("Vehicle follows terrain")
checked: missionItem.followTerrain
onClicked: missionItem.followTerrain = checked
}
GridLayout {
Layout.fillWidth: true
columnSpacing: _margin
rowSpacing: _margin
columns: 2
visible: followsTerrainCheckBox.checked
QGCLabel { text: qsTr("Tolerance") }
FactTextField {
fact: missionItem.terrainAdjustTolerance
Layout.fillWidth: true
}
QGCLabel { text: qsTr("Max Climb Rate") }
FactTextField {
fact: missionItem.terrainAdjustMaxClimbRate
Layout.fillWidth: true
}
QGCLabel { text: qsTr("Max Descent Rate") }
FactTextField {
fact: missionItem.terrainAdjustMaxDescentRate
Layout.fillWidth: true
onTriggered
:
{
if
(
indicator
.
running
==
false
){
indicator
.
visible
=
false
}
}
}
}
}*/
/*SectionHeader {
id: statsHeader
text: qsTr("Statistics")
}*/
//TransectStyleComplexItemStats { }
}
}
// Column
}
// Rectangle
src/Wima/CSWorker.cpp
0 → 100644
View file @
6fd60bb5
This diff is collapsed.
Click to expand it.
src/Wima/CSWorker.h
0 → 100644
View file @
6fd60bb5
#pragma once
#include <QGeoCoordinate>
#include <QList>
#include <QSharedPointer>
#include <QThread>
#include "snake.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
//!
//! \brief The CSWorker class
//! \note Don't call QThread::start, QThread::quit, etc. onyl use Worker
//! members!
class
CSWorker
:
public
QThread
{
Q_OBJECT
using
Lock
=
std
::
unique_lock
<
std
::
mutex
>
;
public:
using
Route
=
QList
<
QGeoCoordinate
>
;
using
PtrRoute
=
QSharedPointer
<
Route
>
;
CSWorker
(
QObject
*
parent
=
nullptr
);
~
CSWorker
()
override
;
bool
calculating
();
public
slots
:
void
update
(
const
QList
<
QGeoCoordinate
>
&
polygon
,
const
QGeoCoordinate
&
origin
,
snake
::
Length
deltaR
,
snake
::
Length
minLength
,
snake
::
Angle
deltaAlpha
);
signals:
void
ready
(
PtrRoute
pTransects
);
void
calculatingChanged
();
protected:
void
run
()
override
;
private:
mutable
std
::
mutex
_mutex
;
mutable
std
::
condition_variable
_cv
;
// Internal data
QList
<
QGeoCoordinate
>
_polygon
;
QGeoCoordinate
_origin
;
snake
::
Length
_deltaR
;
snake
::
Angle
_deltaAlpha
;
snake
::
Length
_minLength
;
std
::
size_t
_maxWaypoints
;
// State
std
::
atomic_bool
_calculating
;
std
::
atomic_bool
_stop
;
std
::
atomic_bool
_restart
;
};
src/Wima/CircularSurvey.cc
View file @
6fd60bb5
This diff is collapsed.
Click to expand it.
src/Wima/CircularSurvey.h
View file @
6fd60bb5
...
...
@@ -6,9 +6,14 @@
#include "SettingsFact.h"
#include "TransectStyleComplexItem.h"
class
CSWorker
;
class
CircularSurvey
:
public
TransectStyleComplexItem
{
Q_OBJECT
public:
using
Route
=
QList
<
QGeoCoordinate
>
;
using
PtrRoute
=
QSharedPointer
<
Route
>
;
/// @param vehicle Vehicle which this is being contructed for
/// @param flyView true: Created for use in the Fly View, false: Created for
/// use in the Plan View
...
...
@@ -16,19 +21,19 @@ public:
/// polygon
CircularSurvey
(
Vehicle
*
vehicle
,
bool
flyView
,
const
QString
&
kmlOrShpFile
,
QObject
*
parent
);
~
CircularSurvey
();
Q_PROPERTY
(
QGeoCoordinate
refPoint
READ
refPoint
WRITE
setRefPoint
NOTIFY
refPointChanged
)
Q_PROPERTY
(
Fact
*
deltaR
READ
deltaR
CONSTANT
)
Q_PROPERTY
(
Fact
*
deltaAlpha
READ
deltaAlpha
CONSTANT
)
Q_PROPERTY
(
Fact
*
transectMinLength
READ
transectMinLength
CONSTANT
)
Q_PROPERTY
(
Fact
*
reverse
READ
reverse
CONSTANT
)
Q_PROPERTY
(
Fact
*
maxWaypoints
READ
maxWaypoints
CONSTANT
)
Q_PROPERTY
(
bool
isInitialized
READ
isInitialized
WRITE
setIsInitialized
NOTIFY
isInitializedChanged
)
Q_PROPERTY
(
bool
calculating
READ
calculating
NOTIFY
calculatingChanged
)
Q_INVOKABLE
void
resetReference
(
void
);
Q_INVOKABLE
void
reverse
(
void
);
// Property setters
void
setRefPoint
(
const
QGeoCoordinate
&
refPt
);
...
...
@@ -41,8 +46,6 @@ public:
Fact
*
deltaR
();
Fact
*
deltaAlpha
();
Fact
*
transectMinLength
();
Fact
*
reverse
();
Fact
*
maxWaypoints
();
bool
calculating
();
// Is true if survey was automatically generated, prevents initialisation from
// gui.
...
...
@@ -72,8 +75,6 @@ public:
static
const
char
*
deltaRName
;
static
const
char
*
deltaAlphaName
;
static
const
char
*
transectMinLengthName
;
static
const
char
*
reverseName
;
static
const
char
*
maxWaypointsName
;
static
const
char
*
CircularSurveyName
;
static
const
char
*
refPointLongitudeName
;
static
const
char
*
refPointLatitudeName
;
...
...
@@ -89,7 +90,7 @@ private slots:
void
_rebuildTransectsPhase1
(
void
)
final
;
void
_recalcComplexDistance
(
void
)
final
;
void
_recalcCameraShots
(
void
)
final
;
void
_
deferUpdate
(
);
void
_
setTransects
(
PtrRoute
pRoute
);
private:
void
_appendLoadedMissionItems
(
QList
<
MissionItem
*>
&
items
,
...
...
@@ -108,21 +109,14 @@ private:
// minimal transect lenght, transects are rejected if they are shorter than
// this value
SettingsFact
_minLength
;
// reverses the _transects path
SettingsFact
_reverse
;
// the maximum number of waypoints _transects (TransectStyleComplexItem) can
// contain (to avoid performance hits)
SettingsFact
_maxWaypoints
;
// Timer to defer recalc
QTimer
_timer
;
// indicates if the polygon and refpoint etc. are initialized, prevents
// reinitialisation from gui and execution of _rebuildTransectsPhase1 during
// init from gui
bool
_isInitialized
;
using
Ptr
Transects
=
std
::
shared_ptr
<
Transects
>
;
using
Watcher
=
QFutureWatcher
<
PtrTransects
>
;
Watcher
_watcher
;
bool
_
calculat
ing
;
bool
_
cancle
;
using
Ptr
Worker
=
std
::
shared_ptr
<
CSWorker
>
;
PtrWorker
_pWorker
;
PtrRoute
_pRoute
;
bool
_
needsStor
ing
;
bool
_
needsReversal
;
};
src/Wima/Snake/snake.cpp
View file @
6fd60bb5
...
...
@@ -781,8 +781,8 @@ void generateRoutingModel(const BoostLineString &vertices,
}
bool
route
(
const
BoostPolygon
&
area
,
const
Transects
&
transects
,
std
::
vector
<
TransectInfo
>
&
transectInfo
,
Route
&
r
oute
,
string
&
errorString
)
{
std
::
vector
<
TransectInfo
>
&
transectInfo
,
Route
&
r
,
st
d
::
function
<
bool
()
>
stop
,
st
ring
&
errorString
)
{
//=======================================
// Route Transects using Google or-tools.
//=======================================
...
...
@@ -819,9 +819,17 @@ bool route(const BoostPolygon &area, const Transects &transects,
vertices
.
push_back
(
vertex
);
}
size_t
n1
=
vertices
.
size
();
if
(
stop
())
{
errorString
=
"User termination."
;
return
false
;
}
// Generate routing model.
RoutingDataModel
dataModel
;
Matrix
<
double
>
connectionGraph
(
n1
,
n1
);
if
(
stop
())
{
errorString
=
"User termination."
;
return
false
;
}
// Offset joined area.
BoostPolygon
areaOffset
;
offsetPolygon
(
area
,
areaOffset
,
detail
::
offsetConstant
);
...
...
@@ -835,6 +843,10 @@ bool route(const BoostPolygon &area, const Transects &transects,
cout
<<
"Execution time _generateRoutingModel(): "
<<
delta
.
count
()
<<
" ms"
<<
endl
;
#endif
if
(
stop
())
{
errorString
=
"User termination."
;
return
false
;
}
// Create Routing Index Manager.
RoutingIndexManager
manager
(
dataModel
.
distanceMatrix
.
getN
(),
...
...
@@ -883,16 +895,13 @@ bool route(const BoostPolygon &area, const Transects &transects,
index
+=
1
;
}
}
// Setting first solution heuristic.
RoutingSearchParameters
searchParameters
=
DefaultRoutingSearchParameters
();
// Set first solution heuristic.
auto
searchParameters
=
DefaultRoutingSearchParameters
();
searchParameters
.
set_first_solution_strategy
(
FirstSolutionStrategy
::
PATH_CHEAPEST_ARC
);
google
::
protobuf
::
Duration
*
tMax
=
new
google
::
protobuf
::
Duration
();
// seconds
tMax
->
set_seconds
(
10
);
searchParameters
.
set_allocated_time_limit
(
tMax
);
// Set costume limit.
auto
*
limit
=
solver
->
MakeCustomLimit
(
stop
);
routing
.
AddSearchMonitor
(
limit
);
// Solve the problem.
#ifdef SNAKE_SHOW_TIME
start
=
std
::
chrono
::
high_resolution_clock
::
now
();
...
...
@@ -908,6 +917,9 @@ bool route(const BoostPolygon &area, const Transects &transects,
if
(
!
solution
||
solution
->
Size
()
<=
1
)
{
errorString
=
"Not able to solve the routing problem."
;
return
false
;
}
else
if
(
stop
())
{
errorString
=
"User terminated."
;
return
false
;
}
// Extract index list from solution.
...
...
@@ -944,12 +956,12 @@ bool route(const BoostPolygon &area, const Transects &transects,
}
for
(
auto
it
=
reversedTransect
.
begin
();
it
<
reversedTransect
.
end
()
-
1
;
++
it
)
{
r
oute
.
push_back
(
*
it
);
r
.
push_back
(
*
it
);
}
}
else
{
const
auto
&
t
=
transects
[
info1
.
index
];
for
(
auto
it
=
t
.
begin
();
it
<
t
.
end
()
-
1
;
++
it
)
{
r
oute
.
push_back
(
*
it
);
r
.
push_back
(
*
it
);
}
}
}
else
{
...
...
@@ -958,12 +970,19 @@ bool route(const BoostPolygon &area, const Transects &transects,
if
(
i
!=
route_idx
.
size
()
-
2
)
{
idxList
.
pop_back
();
}
idx2Vertex
(
idxList
,
r
oute
);
idx2Vertex
(
idxList
,
r
);
}
}
return
true
;
}
bool
route
(
const
BoostPolygon
&
area
,
const
Transects
&
transects
,
std
::
vector
<
TransectInfo
>
&
transectInfo
,
Route
&
r
,
string
&
errorString
)
{
auto
stop
=
[]
{
return
false
;
};
return
route
(
area
,
transects
,
transectInfo
,
r
,
stop
,
errorString
);
}
}
// namespace snake
bool
boost
::
geometry
::
model
::
operator
==
(
snake
::
BoostPoint
p1
,
...
...
src/Wima/Snake/snake.h
View file @
6fd60bb5
#pragma once
#include <array>
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <vector>
...
...
@@ -185,8 +187,11 @@ struct TransectInfo {
bool
reversed
;
};
bool
route
(
const
BoostPolygon
&
area
,
const
Transects
&
transects
,
std
::
vector
<
TransectInfo
>
&
transectInfo
,
Route
&
r
oute
,
std
::
vector
<
TransectInfo
>
&
transectInfo
,
Route
&
r
,
string
&
errorString
);
bool
route
(
const
BoostPolygon
&
area
,
const
Transects
&
transects
,
std
::
vector
<
TransectInfo
>
&
transectInfo
,
Route
&
r
,
std
::
function
<
bool
(
void
)
>
stop
,
string
&
errorString
);
namespace
detail
{
const
double
offsetConstant
=
...
...
src/Wima/WimaPlaner.cc
View file @
6fd60bb5
...
...
@@ -21,12 +21,13 @@ const char *WimaPlaner::areaItemsName = "AreaItems";
const
char
*
WimaPlaner
::
missionItemsName
=
"MissionItems"
;
WimaPlaner
::
WimaPlaner
(
QObject
*
parent
)
:
QObject
(
parent
),
_currentAreaIndex
(
-
1
),
_wimaBridge
(
nullptr
),
_joinedArea
(
this
),
_joinedAreaValid
(
false
),
_measurementArea
(
this
),
_serviceArea
(
this
),
_corridor
(
this
),
_TSComplexItem
(
nullptr
),
_surveyRefChanging
(
false
),
_measurementAreaChanging
(
false
),
_corridorChanging
(
false
),
_serviceAreaChanging
(
false
),
_syncronizedWithController
(
false
),
_readyForSync
(
false
)
{
:
QObject
(
parent
),
_masterController
(
nullptr
),
_missionController
(
nullptr
),
_currentAreaIndex
(
-
1
),
_wimaBridge
(
nullptr
),
_joinedArea
(
this
),
_joinedAreaValid
(
false
),
_arrivalPathLength
(
0
),
_returnPathLength
(
0
),
_TSComplexItem
(
nullptr
),
_surveyRefChanging
(
false
),
_measurementAreaChanging
(
false
),
_corridorChanging
(
false
),
_serviceAreaChanging
(
false
),
_syncronizedWithController
(
false
),
_readyForSync
(
false
)
{
_lastMeasurementAreaPath
=
_measurementArea
.
path
();
_lastServiceAreaPath
=
_serviceArea
.
path
();
_lastCorridorPath
=
_corridor
.
path
();
...
...
src/Wima/WimaPlaner.h
View file @
6fd60bb5
...
...
@@ -49,16 +49,16 @@ public:
Q_PROPERTY
(
bool
readyForSync
READ
readyForSync
NOTIFY
readyForSyncChanged
)
// Property accessors
PlanMasterController
*
masterController
(
void
)
{
return
_masterController
;
}
MissionController
*
missionController
(
void
)
{
return
_missionController
;
}
PlanMasterController
*
masterController
(
void
)
;
MissionController
*
missionController
(
void
)
;
QmlObjectListModel
*
visualItems
(
void
);
int
currentPolygonIndex
(
void
)
const
{
return
_currentAreaIndex
;
}
QString
currentFile
(
void
)
const
{
return
_currentFile
;
}
int
currentPolygonIndex
(
void
)
const
;
QString
currentFile
(
void
)
const
;
QStringList
loadNameFilters
(
void
)
const
;
QStringList
saveNameFilters
(
void
)
const
;
QString
fileExtension
(
void
)
const
{
return
wimaFileExtension
;
}
QString
fileExtension
(
void
)
const
;
QGeoCoordinate
joinedAreaCenter
(
void
)
const
;
WimaBridge
*
wimaBridge
(
void
)
{
return
_wimaBridge
;
}
WimaBridge
*
wimaBridge
(
void
)
;
// Property setters
void
setMasterController
(
PlanMasterController
*
masterController
);
...
...
@@ -145,22 +145,22 @@ private:
PlanMasterController
*
_masterController
;
MissionController
*
_missionController
;
int
_currentAreaIndex
;
QString
_currentFile
;
// file for saveing
WimaBridge
*
_wimaBridge
;
// container for data exchange with WimaController
QmlObjectListModel
_visualItems
;
// contains all visible areas
WimaJoinedArea
_joinedArea
;
// joined area fromed by _measurementArea,
// _serviceArea, _corridor
QString
_currentFile
;
// container for data exchange with WimaController
WimaBridge
*
_wimaBridge
;
bool
_joinedAreaValid
;
WimaMeasurementArea
_measurementArea
;
// measurement area
WimaServiceArea
_serviceArea
;
// area for supplying
WimaCorridor
_corridor
;
// corridor connecting _measurementArea and _serviceArea
unsigned
long
_arrivalPathLength
;
// the number waypoints the arrival path consists of
// (path from takeoff to first measurement point)
unsigned
long
_returnPathLength
;
// the number waypoints the return path consists of
// (path from last measurement point to land)
WimaMeasurementArea
_measurementArea
;
WimaServiceArea
_serviceArea
;
WimaCorridor
_corridor
;
// contains all visible areas
QmlObjectListModel
_visualItems
;
// joined area fromed by _measurementArea, _serviceArea, _corridor
WimaJoinedArea
_joinedArea
;
// path from takeoff to first measurement point
unsigned
long
_arrivalPathLength
;
// path from last measurement point to land
unsigned
long
_returnPathLength
;
CircularSurvey
*
_TSComplexItem
;
// pointer to the CircularSurvey item in
// _missionController.visualItems()
...
...
src/WimaView/CircularSurveyMapVisual.qml
View file @
6fd60bb5
...
...
@@ -7,8 +7,6 @@
*
****************************************************************************/
// original file: SurveyMapVisual.qml
import
QtQuick
2.3
import
QtQuick
.
Controls
1.2
import
QtLocation
5.3
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment