Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Philip Trettner
polymesh
Commits
ec2bde01
Commit
ec2bde01
authored
Jul 25, 2018
by
Christian Mattes
Browse files
First PM version that can do custom attributes
parent
baa18516
Changes
3
Hide whitespace changes
Inline
Side-by-side
src/polymesh/detail/AttributeSerializer.hh
View file @
ec2bde01
#pragma once
#include
<iostream>
#include
<memory>
#include
<typeindex>
#include
"../Mesh.hh"
#include
"../attributes.hh"
namespace
polymesh
...
...
@@ -15,43 +17,42 @@ public:
virtual
~
GenericAttributeSerializer
()
{}
/// can this attribute be (de)serialized by this serializer?
virtual
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
vertex_tag
>
const
&
attr
)
=
0
;
virtual
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
halfedge_tag
>
const
&
attr
)
=
0
;
virtual
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
edge_tag
>
const
&
attr
)
=
0
;
virtual
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
face_tag
>
const
&
attr
)
=
0
;
virtual
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
vertex_tag
>
const
&
attr
)
=
0
;
virtual
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
halfedge_tag
>
const
&
attr
)
=
0
;
virtual
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
edge_tag
>
const
&
attr
)
=
0
;
virtual
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
face_tag
>
const
&
attr
)
=
0
;
virtual
void
serialize
(
std
::
ostream
&
out
,
primitive_attribute_base
<
vertex_tag
>
const
&
attr
)
=
0
;
virtual
void
serialize
(
std
::
ostream
&
out
,
primitive_attribute_base
<
halfedge_tag
>
const
&
attr
)
=
0
;
virtual
void
serialize
(
std
::
ostream
&
out
,
primitive_attribute_base
<
edge_tag
>
const
&
attr
)
=
0
;
virtual
void
serialize
(
std
::
ostream
&
out
,
primitive_attribute_base
<
face_tag
>
const
&
attr
)
=
0
;
virtual
void
deserialize
(
std
::
istream
&
in
,
primitive_attribute_base
<
vertex_tag
>
&
attr
)
=
0
;
virtual
void
deserialize
(
std
::
istream
&
in
,
primitive_attribute_base
<
halfedge_tag
>
&
attr
)
=
0
;
virtual
void
deserialize
(
std
::
istream
&
in
,
primitive_attribute_base
<
edge_tag
>
&
attr
)
=
0
;
virtual
void
deserialize
(
std
::
istream
&
in
,
primitive_attribute_base
<
face_tag
>
&
attr
)
=
0
;
virtual
std
::
type_index
vertexAttributeType
()
=
0
;
// pointer to tag as dummy parameter to allow deserialize to be used in templates over the tag type
virtual
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
vertex_tag
*
)
=
0
;
virtual
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
halfedge_tag
*
)
=
0
;
virtual
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
edge_tag
*
)
=
0
;
virtual
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
face_tag
*
)
=
0
;
};
template
<
typename
T
,
typename
S
er
D
es
>
class
AttributeSerializer
:
GenericAttributeSerializer
template
<
typename
T
,
typename
s
er
d
es
>
class
AttributeSerializer
final
:
public
GenericAttributeSerializer
{
public:
AttributeSerializer
(
S
er
D
es
const
&
ser
des
)
:
mSerdes
(
ser
des
)
{}
AttributeSerializer
(
s
er
d
es
const
&
ser
ializer
)
:
mSerdes
(
ser
ializer
)
{}
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
vertex_tag
>
const
&
attr
)
override
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
vertex_tag
>
const
&
attr
)
override
{
return
dynamic_cast
<
primitive_attribute
<
vertex_tag
,
T
>
const
*>
(
&
attr
);
}
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
halfedge_tag
>
const
&
attr
)
override
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
halfedge_tag
>
const
&
attr
)
override
{
return
dynamic_cast
<
primitive_attribute
<
halfedge_tag
,
T
>
const
*>
(
&
attr
);
}
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
edge_tag
>
const
&
attr
)
override
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
edge_tag
>
const
&
attr
)
override
{
return
dynamic_cast
<
primitive_attribute
<
edge_tag
,
T
>
const
*>
(
&
attr
);
}
bool
is
C
ompatible
T
o
(
primitive_attribute_base
<
face_tag
>
const
&
attr
)
override
bool
is
_c
ompatible
_t
o
(
primitive_attribute_base
<
face_tag
>
const
&
attr
)
override
{
return
dynamic_cast
<
primitive_attribute
<
face_tag
,
T
>
const
*>
(
&
attr
);
}
...
...
@@ -62,19 +63,52 @@ public:
mSerdes
.
serialize
(
out
,
specific
.
data
(),
specific
.
size
());
}
void
deserialize
(
std
::
istream
&
in
,
primitive_attribute_base
<
vertex_tag
>
&
attr
)
override
void
serialize
(
std
::
ostream
&
out
,
primitive_attribute_base
<
halfedge_tag
>
const
&
attr
)
override
{
auto
const
&
specific
=
dynamic_cast
<
primitive_attribute
<
halfedge_tag
,
T
>
const
&>
(
attr
);
mSerdes
.
serialize
(
out
,
specific
.
data
(),
specific
.
size
());
}
void
serialize
(
std
::
ostream
&
out
,
primitive_attribute_base
<
edge_tag
>
const
&
attr
)
override
{
auto
const
&
specific
=
dynamic_cast
<
primitive_attribute
<
edge_tag
,
T
>
const
&>
(
attr
);
mSerdes
.
serialize
(
out
,
specific
.
data
(),
specific
.
size
());
}
void
serialize
(
std
::
ostream
&
out
,
primitive_attribute_base
<
face_tag
>
const
&
attr
)
override
{
auto
&
specific
=
dynamic_cast
<
primitive_attribute
<
vertex
_tag
,
T
>
&>
(
attr
);
mSerdes
.
de
serialize
(
in
,
specific
.
data
(),
specific
.
size
());
auto
const
&
specific
=
dynamic_cast
<
primitive_attribute
<
face
_tag
,
T
>
const
&>
(
attr
);
mSerdes
.
serialize
(
out
,
specific
.
data
(),
specific
.
size
());
}
std
::
type_index
vertexAttributeType
()
override
{
return
typeid
(
primitive_attribute
<
vertex_tag
,
T
>
);
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
vertex_tag
*
)
override
{
auto
attr
=
mesh
.
vertices
().
make_attribute
<
T
>
();
mSerdes
.
deserialize
(
in
,
attr
.
data
(),
attr
.
size
());
attrs
[
name
]
=
attr
;
}
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
halfedge_tag
*
)
override
{
auto
attr
=
mesh
.
halfedges
().
make_attribute
<
T
>
();
mSerdes
.
deserialize
(
in
,
attr
.
data
(),
attr
.
size
());
attrs
[
name
]
=
attr
;
}
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
edge_tag
*
)
override
{
auto
attr
=
mesh
.
edges
().
make_attribute
<
T
>
();
mSerdes
.
deserialize
(
in
,
attr
.
data
(),
attr
.
size
());
attrs
[
name
]
=
attr
;
}
void
deserialize
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
std
::
string
const
&
name
,
face_tag
*
)
override
{
auto
attr
=
mesh
.
faces
().
make_attribute
<
T
>
();
mSerdes
.
deserialize
(
in
,
attr
.
data
(),
attr
.
size
());
attrs
[
name
]
=
attr
;
}
private:
S
er
D
es
mSerdes
;
s
er
d
es
mSerdes
;
};
}
}
src/polymesh/formats/pm.cc
View file @
ec2bde01
#include
"pm.hh"
#include
<iostream>
#include
<fstream>
#include
<iostream>
#include
<mutex>
#include
<typeindex>
#include
<unordered_map>
...
...
@@ -8,28 +8,35 @@
namespace
polymesh
{
static
std
::
unordered_map
<
std
::
string
,
std
::
unique_ptr
<
detail
::
GenericAttributeSerializer
>>
sSerializersByName
;
static
std
::
unordered_map
<
std
::
type_index
,
detail
::
GenericAttributeSerializer
*>
sSerializersByTypeIndex
;
static
std
::
mutex
sSerializersMutex
;
static
std
::
unordered_map
<
std
::
string
,
std
::
unique_ptr
<
detail
::
GenericAttributeSerializer
>>
sSerializers
;
void
detail
::
register_attribute_serializer
(
const
std
::
string
&
identifier
,
std
::
unique_ptr
<
detail
::
GenericAttributeSerializer
>
ptr
)
{
sSerializers
[
identifier
]
=
std
::
move
(
ptr
);
}
void
registerAttributeSerializer
(
const
std
::
string
&
identifier
,
std
::
unique_ptr
<
detail
::
GenericAttributeSerializer
>
ptr
)
template
<
class
Tag
>
std
::
pair
<
std
::
string
,
detail
::
GenericAttributeSerializer
*>
find_serializer_for
(
primitive_attribute_base
<
Tag
>
const
&
attr
)
{
std
::
lock_guard
<
std
::
mutex
>
_
(
sSerializersMutex
);
sSerializersByTypeIndex
[
ptr
->
vertexAttributeType
()]
=
ptr
.
get
();
sSerializersByName
[
identifier
]
=
std
::
move
(
ptr
);
for
(
auto
const
&
pair
:
sSerializers
)
{
if
(
pair
.
second
->
is_compatible_to
(
attr
))
return
{
pair
.
first
,
pair
.
second
.
get
()};
}
return
{{},
nullptr
};
}
struct
PMHeader
{
char
pm
[
4
]
=
{
'P'
,
'M'
,
0
,
0
};
u
int32_t
num_vertices
;
u
int32_t
num_halfedges
;
u
int32_t
num_faces
;
int32_t
num_vertices
;
int32_t
num_halfedges
;
int32_t
num_faces
;
u
int32_t
num_vertex_attributes
;
u
int32_t
num_halfedge_attributes
;
u
int32_t
num_edge_attributes
;
u
int32_t
num_face_attributes
;
int32_t
num_vertex_attributes
;
int32_t
num_halfedge_attributes
;
int32_t
num_edge_attributes
;
int32_t
num_face_attributes
;
bool
valid
()
const
{
return
pm
[
0
]
==
'P'
&&
pm
[
1
]
==
'M'
&&
pm
[
2
]
==
0
&&
pm
[
3
]
==
0
;
}
};
...
...
@@ -38,15 +45,65 @@ template <class Tag>
static
std
::
istream
&
read_index
(
std
::
istream
&
in
,
primitive_index
<
Tag
>
&
idx
)
{
int32_t
val
;
in
.
read
(
reinterpret_cast
<
char
*>
(
&
val
),
sizeof
(
int32_t
));
in
.
read
(
reinterpret_cast
<
char
*>
(
&
val
),
sizeof
(
int32_t
));
idx
.
value
=
val
;
return
in
;
}
template
<
class
Tag
>
static
std
::
ostream
&
write_index
(
std
::
ostream
&
out
,
primitive_index
<
Tag
>
const
&
idx
)
{
static
std
::
ostream
&
write_index
(
std
::
ostream
&
out
,
primitive_index
<
Tag
>
const
&
idx
)
{
const
int32_t
val
=
idx
.
value
;
return
out
.
write
(
reinterpret_cast
<
char
const
*>
(
&
val
),
sizeof
(
int32_t
));
return
out
.
write
(
reinterpret_cast
<
char
const
*>
(
&
val
),
sizeof
(
int32_t
));
}
static
std
::
ostream
&
write_string
(
std
::
ostream
&
out
,
std
::
string
const
&
text
)
{
return
out
.
write
(
text
.
c_str
(),
text
.
size
()
+
1
);
}
static
std
::
istream
&
read_string
(
std
::
istream
&
in
,
std
::
string
&
text
)
{
return
std
::
getline
(
in
,
text
,
'\0'
);
}
template
<
class
Tag
>
static
std
::
ostream
&
storeAttributes
(
std
::
ostream
&
out
,
std
::
map
<
std
::
string
,
std
::
unique_ptr
<
primitive_attribute_base
<
Tag
>>>
const
&
attrs
)
{
for
(
auto
const
&
attr
:
attrs
)
{
auto
const
&
ser
=
find_serializer_for
(
*
attr
.
second
);
if
(
ser
.
second
)
{
write_string
(
out
,
attr
.
first
);
// Attribute Name
write_string
(
out
,
ser
.
first
);
// Attribute Type
ser
.
second
->
serialize
(
out
,
*
attr
.
second
);
// Attribute Data
}
else
{
std
::
cout
<<
"polymesh::write_pm: "
<<
attr
.
first
<<
" has unregistered type and is not going to be written."
<<
std
::
endl
;
}
}
return
out
;
}
template
<
class
Tag
>
static
bool
restoreAttributes
(
std
::
istream
&
in
,
Mesh
const
&
mesh
,
attribute_collection
&
attrs
,
uint32_t
count
)
{
using
TagPtr
=
Tag
*
;
for
(
uint32_t
i
=
0
;
i
<
count
;
i
++
)
{
std
::
string
attrName
,
attrType
;
read_string
(
in
,
attrName
);
read_string
(
in
,
attrType
);
auto
it
=
sSerializers
.
find
(
attrType
);
if
(
it
!=
sSerializers
.
end
())
{
it
->
second
->
deserialize
(
in
,
mesh
,
attrs
,
attrName
,
TagPtr
{});
}
else
{
std
::
cout
<<
"polymesh::read_pm: "
<<
attrName
<<
" has unregistered type "
<<
attrType
<<
", unable to restore remaining attributes."
<<
std
::
endl
;
return
false
;
}
}
return
true
;
}
void
write_pm
(
std
::
ostream
&
out
,
const
Mesh
&
mesh
,
const
attribute_collection
&
attributes
)
...
...
@@ -60,25 +117,32 @@ void write_pm(std::ostream &out, const Mesh &mesh, const attribute_collection &a
header
.
num_vertices
=
mesh
.
all_vertices
().
size
();
header
.
num_halfedges
=
mesh
.
all_halfedges
().
size
();
header
.
num_faces
=
mesh
.
all_faces
().
size
();
header
.
num_vertex_attributes
=
attributes
.
vertex_attributes
().
size
();
header
.
num_halfedge_attributes
=
attributes
.
halfedge_attributes
().
size
();
header
.
num_edge_attributes
=
attributes
.
edge_attributes
().
size
();
header
.
num_face_attributes
=
attributes
.
face_attributes
().
size
();
out
.
write
(
reinterpret_cast
<
char
*>
(
&
header
),
sizeof
(
header
));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
write_index
(
out
,
ll
.
face_of
(
halfedge_index
(
i
)));
// Store mesh topology
for
(
int
i
=
0
;
i
<
header
.
num_faces
;
++
i
)
write_index
(
out
,
ll
.
halfedge_of
(
face_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_vertices
;
i
++
)
write_index
(
out
,
ll
.
outgoing_halfedge_of
(
vertex_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
write_index
(
out
,
ll
.
to_vertex_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
write_index
(
out
,
ll
.
face_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
write_index
(
out
,
ll
.
next_halfedge_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
write_index
(
out
,
ll
.
prev_halfedge_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_faces
;
++
i
)
write_index
(
out
,
ll
.
halfedge_of
(
face_index
(
i
)
));
for
(
int
i
=
0
;
i
<
header
.
num_vertices
;
i
++
)
write_index
(
out
,
ll
.
outgoing_halfedge_of
(
vertex_index
(
i
)
));
// Store attributes
storeAttributes
(
out
,
attributes
.
vertex_attributes
(
));
storeAttributes
(
out
,
attributes
.
halfedge_attributes
());
storeAttributes
(
out
,
attributes
.
edge_attributes
());
storeAttributes
(
out
,
attributes
.
face_attributes
(
));
}
bool
read_pm
(
std
::
istream
&
input
,
Mesh
&
mesh
,
attribute_collection
&
attributes
)
...
...
@@ -91,23 +155,25 @@ bool read_pm(std::istream &input, Mesh &mesh, attribute_collection &attributes)
auto
ll
=
low_level_api
(
mesh
);
ll
.
alloc_primitives
(
header
.
num_vertices
,
header
.
num_faces
,
header
.
num_halfedges
);
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
read_index
(
input
,
ll
.
face_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_faces
;
++
i
)
read_index
(
input
,
ll
.
halfedge_of
(
face_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_vertices
;
i
++
)
read_index
(
input
,
ll
.
outgoing_halfedge_of
(
vertex_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
read_index
(
input
,
ll
.
to_vertex_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
read_index
(
input
,
ll
.
face_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
read_index
(
input
,
ll
.
next_halfedge_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_halfedges
;
++
i
)
read_index
(
input
,
ll
.
prev_halfedge_of
(
halfedge_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_faces
;
++
i
)
re
ad_index
(
input
,
ll
.
halfedge_of
(
face_index
(
i
)));
for
(
int
i
=
0
;
i
<
header
.
num_vertices
;
i
++
)
read_index
(
input
,
ll
.
outgoing_halfedge_of
(
vertex_index
(
i
))
);
// Store attributes
re
storeAttributes
<
vertex_tag
>
(
input
,
mesh
,
attributes
,
header
.
num_vertex_attributes
)
&&
restoreAttributes
<
halfedge_tag
>
(
input
,
mesh
,
attributes
,
header
.
num_halfedge_attributes
)
&&
restoreAttributes
<
edge_tag
>
(
input
,
mesh
,
attributes
,
header
.
num_edge_attributes
)
&&
restoreAttributes
<
face_tag
>
(
input
,
mesh
,
attributes
,
header
.
num_face_attributes
);
return
!
input
.
fail
();
}
...
...
@@ -123,5 +189,4 @@ bool read_pm(const std::string &filename, Mesh &mesh, attribute_collection &attr
std
::
ifstream
in
(
filename
,
std
::
ios
::
binary
);
return
read_pm
(
in
,
mesh
,
attributes
);
}
}
src/polymesh/formats/pm.hh
View file @
ec2bde01
...
...
@@ -24,17 +24,31 @@ namespace detail
template
<
typename
T
>
struct
bytewise_serdes
{
void
serialize
(
std
::
ostream
&
out
,
T
const
*
data
,
size_t
num_items
)
{
out
.
write
(
static
_cast
<
char
const
*>
(
data
),
num_items
*
sizeof
(
T
));
}
void
deserialize
(
std
::
istream
&
in
,
T
*
data
,
size_t
num_items
)
{
in
.
read
(
static
_cast
<
char
*>
(
data
),
num_items
*
sizeof
(
T
));
}
void
serialize
(
std
::
ostream
&
out
,
T
const
*
data
,
size_t
num_items
)
const
{
out
.
write
(
reinterpret
_cast
<
char
const
*>
(
data
),
num_items
*
sizeof
(
T
));
}
void
deserialize
(
std
::
istream
&
in
,
T
*
data
,
size_t
num_items
)
const
{
in
.
read
(
reinterpret
_cast
<
char
*>
(
data
),
num_items
*
sizeof
(
T
));
}
};
void
registerAttributeSerializer
(
std
::
string
const
&
identifier
,
std
::
unique_ptr
<
detail
::
GenericAttributeSerializer
>
ptr
);
struct
string_serdes
{
void
serialize
(
std
::
ostream
&
out
,
std
::
string
const
*
strings
,
size_t
num_items
)
const
{
for
(
size_t
i
=
0
;
i
<
num_items
;
i
++
)
out
.
write
(
strings
[
i
].
c_str
(),
strings
[
i
].
size
()
+
1
);
}
void
deserialize
(
std
::
istream
&
in
,
std
::
string
*
strings
,
size_t
num_items
)
const
{
for
(
size_t
i
=
0
;
i
<
num_items
;
i
++
)
std
::
getline
(
in
,
strings
[
i
],
'\0'
);
}
};
void
register_attribute_serializer
(
std
::
string
const
&
identifier
,
std
::
unique_ptr
<
detail
::
GenericAttributeSerializer
>
ptr
);
}
template
<
typename
T
,
typename
S
er
D
es
>
void
register
T
ype
(
std
::
string
const
&
identifier
,
S
er
D
es
&&
serializer
=
detail
::
bytewise_serdes
<
T
>
{})
template
<
typename
T
,
typename
serdes
=
detail
::
bytewise_s
er
d
es
<
T
>
>
void
register
_t
ype
(
std
::
string
const
&
identifier
,
s
er
d
es
&&
serializer
=
detail
::
bytewise_serdes
<
T
>
{})
{
auto
ptr
=
std
::
make_unique
<
detail
::
AttributeSerializer
<
T
,
S
er
D
es
>>
(
serializer
);
detail
::
register
A
ttribute
S
erializer
(
identifier
,
std
::
move
(
ptr
));
auto
ptr
=
std
::
make_unique
<
detail
::
AttributeSerializer
<
T
,
s
er
d
es
>>
(
serializer
);
detail
::
register
_a
ttribute
_s
erializer
(
identifier
,
std
::
move
(
ptr
));
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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