Commit Graph

3654 Commits

Author SHA1 Message Date
Hans Goudey
ab8acbbfe5 Cleanup: Use curve positions accessor function
There's no particular reason to use the attribute API instead here.
2023-04-17 23:38:10 -04:00
Hans Goudey
c234a802ba Cleanup: Remove unused using keyword 2023-04-17 23:38:10 -04:00
Hans Goudey
2a4323c2f5 Mesh: Move edges to a generic attribute
Implements #95966, as the final step of #95965.

This commit changes the storage of mesh edge vertex indices from the
`MEdge` type to the generic `int2` attribute type. This follows the
general design for geometry and the attribute system, where the data
storage type and the usage semantics are separated.

The main benefit of the change is reduced memory usage-- the
requirements of storing mesh edges is reduced by 1/3. For example,
this saves 8MB on a 1 million vertex grid. This also gives performance
benefits to any memory-bound mesh processing algorithm that uses edges.

Another benefit is that all of the edge's vertex indices are
contiguous. In a few cases, it's helpful to process all of them as
`Span<int>` rather than `Span<int2>`. Similarly, the type is more
likely to match a generic format used by a library, or code that
shouldn't know about specific Blender `Mesh` types.

Various Notes:
- The `.edge_verts` name is used to reflect a mapping between domains,
  similar to `.corner_verts`, etc. The period means that it the data
  shouldn't change arbitrarily by the user or procedural operations.
- `edge[0]` is now used instead of `edge.v1`
- Signed integers are used instead of unsigned to reduce the mixing
  of signed-ness, which can be error prone.
- All of the previously used core mesh data types (`MVert`, `MEdge`,
  `MLoop`, `MPoly` are now deprecated. Only generic types are used).
- The `vec2i` DNA type is used in the few C files where necessary.

Pull Request: https://projects.blender.org/blender/blender/pulls/106638
2023-04-17 13:47:41 +02:00
Jacques Lucke
5491563e59 Fix #106982: crash with muted node
The lazy function for muted nodes did request inputs
even if they were not required.
2023-04-17 10:59:05 +02:00
illua1
15f464019a Geometry Nodes: avoid last buffer copy in Blur Attribute node
Previously, there was a "main" and "tmp" buffer and the final
result was expected to be in the "main" buffer. Now the two buffers
are called a and b and the final result can be in either of those.

This can improve performance especially if the number of iterations is low.

Pull Request: https://projects.blender.org/blender/blender/pulls/106860
2023-04-17 08:08:46 +02:00
Erik Abrahamsson
2fade47a9d Fix: Transform geometry node doesn't translate volumes correctly
Fixes a bug introduced in b0b9e746fa.
The volume transformation matrix is multiplied in the wrong order
which means the grid scale is applied on the translation.
2023-04-17 03:10:40 +02:00
Hans Goudey
8df6974a15 Fix #106879: Texture Node add search is broken 2023-04-14 13:52:13 -04:00
Hans Goudey
dcb3b1c1f9 Geometry: Use implicit sharing for curve and mesh offsets
Similar to 7eee378ecc, this change decreases memory usage and
improves performance when copying curves and meshes without changing
their topology. The same change used for custom data layers is applied
to face and curve offset indices, which aren't stored as a custom data
layer.

The implicit sharing info for the offsets is stored in the mesh and
curve runtime structs, since it doesn't need to be written to files
directly. When changing the offsets pointer directly, the sharing info
must be updated accordingly. To make that easier, a few utility
functions take care of common operations like making an array mutable,
resizing an array, and creating sharing info for allocated data.

This commit also clarifies the intention to not allocate the offsets
at all when there are no curves/faces. That slightly complicates some
of the logic, but there's no reason for the single `0` integer to be
allocated.

Pull Request: https://projects.blender.org/blender/blender/pulls/106907
2023-04-14 17:58:13 +02:00
Hans Goudey
80f3f59555 Fix: Remove unsupported data types in extrude and split edges nodes
The extrude node resizes an existing mesh, but doesn't initialize new
data for most non-generic data types like shape keys or freestyle tags,
or custom normals. The split edges node doesn't process some
similar vertex data too.

In the future this data can become generic attributes, or it can be
supported in the nodes anyway. But now the new data is un-initialized
after being allocated.

Fixes #106926
2023-04-14 10:06:48 -04:00
Hans Goudey
197e9b9f80 Cleanup: Use more descriptive function name in extrude node 2023-04-13 12:49:16 -04:00
Hans Goudey
a7bee90c1d Cleanup: Add access method for point cloud positions
The position attribute has special meaning for point clouds, and
meshes and curves have access methods for the attribute as well.
This saves boilerplate and gives more consistency between types.
2023-04-13 12:49:16 -04:00
Campbell Barton
61cb302dd5 Cleanup: sort cmake file lists 2023-04-13 13:14:11 +10:00
Hans Goudey
16de3ba759 Fix #106845: Extrude node crash when no faces added
The non-individual face mode needs to check if there are any new faces
before accumulating their corner offsets.
2023-04-12 15:40:55 -04:00
Damien Picard
5cf365554b I18n: disambiguate a few messages
- "Lens" can be a transparent object used in cameras, or specifically
  its property of focal length
- "Empty" can be an adjective meaning void, or an object type. The
  latter is already disambiguated using `ID_ID`
- "New" and "Old" are adjectives that can have agreements in some
  languages
- "Modified" is an adjective that can have agreement in some languages
- "Clipping" can be a property of a camera, or a behavior of the
  mirror modifier
- "Value" in HSV nodes, see #105113
- "Area" in the Face Area geometry node, can mean a measurement or a
  window type
- "New" is an adjective that can have agreement
- "Tab" can be a UI element or a whitespace character
- "Volume" can mean a measurement or an object type. The latter is
  already disambiguated using `ID_ID`

These changes introduce the new `BLT_I18NCONTEXT_TIME` translation
context.

They also remove `BLT_I18NCONTEXT_VIRTUAL_REALITY`, which I added at
one point but then couldn't find which messages I wanted to fix with
it.

Ref #43295

Pull Request: #106718
2023-04-12 15:09:17 +02:00
Campbell Barton
ccea39b538 Cleanup: spelling in comments 2023-04-12 11:24:10 +10:00
zanqdo
5df8e35da7 UI: Add slash separators to Hue/Saturation/Value
It was that names of related "combo" operations
like Hue/Saturation/Value, *Dilate/Erode*
and Brightness/Contrast should be separated
by slashes in their names.

This patch changes this for the multiple nodes
and operators concerning Hue/Saturation/Value
across Blender.

Note1: This patch should only touch UI names
which do not need versioning and should not
break scripts.

Note2: This breaks first letter fuzzy search
for "hsv". It was noted by @HooglyBoogly that
the "/" character needs to be added to the
fuzzy search split list. Note however that such
search is already broken in Main for nodes like
Brightness/Contrast and Dilate/Erode which
already use slash separators

Pull Request: https://projects.blender.org/blender/blender/pulls/106721
2023-04-11 23:48:05 +02:00
Hans Goudey
4170545dc5 Fix #106828: Extrude individual mode crash on mesh with no faces
Caused by 7966cd16d6
2023-04-11 15:34:03 -04:00
zanqdo
f8108d6dfd UI: Rename Bright/Contrast to Brightness/Contrast
Rename *Bright/Contrast* to *Brightness/Contrast* in order to avoid the use of shortened names and improve consistency within Blender and with industry conventions.

Reasoning:

The modified color characteristic is called *brightness*, not *bright*. You don't modify the *bright* of an image.

This also interferes with search in case someone searches for brightness, producing no results.

*Note: This patch should only touch UI names which do not need versioning. It leaves the actual property name in nodes for a future breaking release.*

Pull Request: https://projects.blender.org/blender/blender/pulls/104998
2023-04-11 21:32:29 +02:00
Omar Emara
0c01e0f489 Realtime Compositor: Implement Map UV node
This patch implements the Map UV node for the realtime compositor. This
is different from the CPU implementation in that it relies on the
approximate EWA filtering provided by the driver's implementation of
anisotropic filtering to displace the texture. This is easier and faster
than implementing EWA manually in the shader and has identical quality
where it matters.

Pull Request: https://projects.blender.org/blender/blender/pulls/106765
2023-04-10 17:29:39 +02:00
Campbell Barton
4bbbad49c8 Fix missing includes 2023-04-10 15:10:04 +10:00
Omar Emara
cc5128f6ca Realtime Compositor: Implement Fog Glow Glare node
This patch implements the Fog Glow option in the Glare node. The
implementation does not match the existing implementation in the CPU
compositor, because it is computationally infeasible for the realtime
compositor. Instead, this implementation is similar to how Bloom is
implemented in EEVEE, based on the talk:

"Next Generation Post Processing in Call of Duty: Advanced Warfare".

This is just a temporary solution as requested by users until we either
implement an accurate implementation using FFT, provide a separate
option for EEVEE bloom, or roll out a different solution altogether.

Pull Request: https://projects.blender.org/blender/blender/pulls/106042
2023-04-09 15:42:20 +02:00
Omar Emara
625ba3cb5d Realtime Compositor: Implement Displace node
This patch implements the Displace node for the realtime compositor.
This is different from the CPU implementation in that it relies on the
approximate EWA filtering provided by the driver's implementation of
anisotropic filtering to displace the texture. This is easier and faster
than implementing EWA manually in the shader and has identical quality
where it matters.

Pull Request: https://projects.blender.org/blender/blender/pulls/106681
2023-04-09 09:15:28 +02:00
Omar Emara
b939b60c3f Realtime Compositor: Implement Z Combine node
This patch implements the Z Combine node for the realtime compositor.
The patch also extends the SMAA implementation to work with float
textures as a prerequisite to the Z Combine implementation. Moreover, a
mechanism for computing multi-output operations was implemented, in
which unneeded outputs will allocate a dummy 1x1 texture for a correct
shader invocation, then those dummy textures will be cleaned up by
calling a routine right after evaluation.

This is different from the CPU implementation in that the while combine
mask is anti-aliased, including the alpha mask, which is not considered
in the CPU case.

The node can be implemented as a GPU shader operation when the
anti-aliasing option is disabled, which is something we should do when
the evaluator allows nodes be executed as both standard and GPU shader
operations.

Pull Request: https://projects.blender.org/blender/blender/pulls/106637
2023-04-09 09:06:41 +02:00
Hans Goudey
11ba8c6ab4 Fix #106622: Various errors filling cone/cylinder primitive faces
The change in 7966cd16d6 had a few logical mistakes.
2023-04-06 12:48:46 -04:00
Hans Goudey
6cb5b14f16 Fix #106598: Cone primitive crash after MPoly removal
The sizes of the bottom faces weren't filled.
2023-04-05 14:11:18 -04:00
Hans Goudey
7966cd16d6 Mesh: Replace MPoly struct with offset indices
Implements #95967.

Currently the `MPoly` struct is 12 bytes, and stores the index of a
face's first corner and the number of corners/verts/edges. Polygons
and corners are always created in order by Blender, meaning each
face's corners will be after the previous face's corners. We can take
advantage of this fact and eliminate the redundancy in mesh face
storage by only storing a single integer corner offset for each face.
The size of the face is then encoded by the offset of the next face.
The size of a single integer is 4 bytes, so this reduces memory
usage by 3 times.

The same method is used for `CurvesGeometry`, so Blender already has
an abstraction to simplify using these offsets called `OffsetIndices`.
This class is used to easily retrieve a range of corner indices for
each face. This also gives the opportunity for sharing some logic with
curves.

Another benefit of the change is that the offsets and sizes stored in
`MPoly` can no longer disagree with each other. Storing faces in the
order of their corners can simplify some code too.

Face/polygon variables now use the `IndexRange` type, which comes with
quite a few utilities that can simplify code.

Some:
- The offset integer array has to be one longer than the face count to
  avoid a branch for every face, which means the data is no longer part
  of the mesh's `CustomData`.
- We lose the ability to "reference" an original mesh's offset array
  until more reusable CoW from #104478 is committed. That will be added
  in a separate commit.
- Since they aren't part of `CustomData`, poly offsets often have to be
  copied manually.
- To simplify using `OffsetIndices` in many places, some functions and
  structs in headers were moved to only compile in C++.
- All meshes created by Blender use the same order for faces and face
  corners, but just in case, meshes with mismatched order are fixed by
  versioning code.
- `MeshPolygon.totloop` is no longer editable in RNA. This API break is
  necessary here unfortunately. It should be worth it in 3.6, since
  that's the best way to allow loading meshes from 4.0, which is
  important for an LTS version.

Pull Request: https://projects.blender.org/blender/blender/pulls/105938
2023-04-04 20:39:28 +02:00
Omar Emara
ff3b2226fb GPU: Refactor texture samplers
This patch refactors the texture samples code by mainly splitting the
eGPUSamplerState enum into multiple smaller enums and packing them
inside a GPUSamplerState struct. This was done because many members of
the enum were mutually exclusive, which was worked around during setting
up the samplers in the various backends, and additionally made the API
confusing, like the GPU_texture_wrap_mode function, which had two
mutually exclusive parameters.

The new structure also improved and clarified the backend sampler cache,
reducing the cache size from 514 samplers to just 130 samplers, which
also slightly improved the initialization time. Further, the
GPU_SAMPLER_MAX signal value was naturally incorporated into the
structure using the GPU_SAMPLER_STATE_TYPE_INTERNAL type.

The only expected functional change is in the realtime compositor, which
now supports per-axis repetition control, utilizing new API functions
for that purpose.

This patch is loosely based on an older patch D14366 by Ethan Hall.

Pull Request: https://projects.blender.org/blender/blender/pulls/105642
2023-04-04 15:16:07 +02:00
illua1
9726e4a0ad Nodes: Move data-block default values with link drag search
When creating nodes by dragging a link, it can be convenient to
transfer values from input socket. For reference values, like images,
this may be necessary to avoid unnecessary data-block users. This
patch starts adding such a system. At this moment this only makes sense
for one node (Image Input), but this can be extended to work with other
reference types, different non-reference types and support auto-casting
(if a float is transferred to the Integer Input node).

See task: https://projects.blender.org/blender/blender/issues/102854
Original patch: https://archive.blender.org/developer/D16735

Pull Request: https://projects.blender.org/blender/blender/pulls/105972
2023-04-03 19:33:39 +02:00
Hans Goudey
e7f395dd20 Nodes: Add dropdown to select group socket subtype
Previously the only way to control the subtype was to remove the group
input or output and create it again. This commit adds a dropdown to
change an existing socket, for supported socket types.

Based on a patch by Angus Stanton: https://developer.blender.org/D15715

It was necessary to fix the UI code slightly; the layout's context
wasn't being used in calls to an operator's enum items callback.

Pull Request: https://projects.blender.org/blender/blender/pulls/105614
2023-04-03 18:23:30 +02:00
Jacques Lucke
a145d1563a Cleanup: move flip faces code from nodes to geometry module 2023-03-30 18:44:11 +02:00
Sergey Sharybin
a12a8a71bb Remove "All Rights Reserved" from Blender Foundation copyright code
The goal is to solve confusion of the "All rights reserved" for licensing
code under an open-source license.

The phrase "All rights reserved" comes from a historical convention that
required this phrase for the copyright protection to apply. This convention
is no longer relevant.

However, even though the phrase has no meaning in establishing the copyright
it has not lost meaning in terms of licensing.

This change makes it so code under the Blender Foundation copyright does
not use "all rights reserved". This is also how the GPL license itself
states how to apply it to the source code:

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software ...

This change does not change copyright notice in cases when the copyright
is dual (BF and an author), or just an author of the code. It also does
mot change copyright which is inherited from NaN Holding BV as it needs
some further investigation about what is the proper way to handle it.
2023-03-30 10:51:59 +02:00
Sergey Sharybin
d32d787f5f Clang-Format: Allow empty functions to be single-line
For example

```
OIIOOutputDriver::~OIIOOutputDriver()
{
}
```

becomes

```
OIIOOutputDriver::~OIIOOutputDriver() {}
```

Saves quite some vertical space, which is especially handy for
constructors.

Pull Request: https://projects.blender.org/blender/blender/pulls/105594
2023-03-29 16:50:54 +02:00
Campbell Barton
1ddbe7cadd Cleanup: move doc-strings into headers, remove duplicates
In some cases move implementation details into the function body.
2023-03-29 14:37:34 +11:00
Hans Goudey
59c0e19db2 Geometry: Skip recomputing bounds after translation
Now that mesh primitive nodes calculate their bounds in constant time,
it's worth updating the bounds eagerly when translating a geometry
since doing so should be very cheap and might save a more significant
amount of time if they're needed later.

Also slightly unify the translation functions for the different types.

Pull Request: https://projects.blender.org/blender/blender/pulls/106159
2023-03-27 20:51:01 +02:00
Omar Emara
2f4a7d67b7 Realtime Compositor: Implement Anti-Aliasing node
This patch implements the Anti-Aliasing node by porting SMAA from
Workbench into a generic library that can be used by the realtime
compositor and potentially other users. SMAA was encapsulated in an
algorithm to prepare it for use by other nodes that require SMAA
support.

Pull Request: https://projects.blender.org/blender/blender/pulls/106114
2023-03-26 16:59:13 +02:00
Omar Emara
9678e6e7e5 Fix: Variable size blur implicitly works with Fast Gaussian
If the variable size option of the Blur node was previously set to true
and the type of blur was later set to Fast Gaussian, variable size blur
will take effect, even though the option is not visible to the user.

This patch fixes the issue by disabling variable size blur when the blur
type is set to Fast Gaussian.
2023-03-25 11:41:22 +02:00
Daniel Salazar
c971adfeef Separate ColorRamp node name into Color Ramp
Node names should be properly separated for easy searching and reading
and for consistency with other (color) nodes.

This commit should only touch UI names which do not need versioning and does not break node setups or py scripts.
2023-03-24 05:00:02 +01:00
Hans Goudey
5c3f4195b6 Cleanup: Move poly topology lookup functions to C++ header
Standardize naming, use spans and references for input parameters,
and improve documentation. Now the functions expect the lookups to
succeed as well, they will fail and assert otherwise.

The functions are also simple enough that it likely makes sense to keep
them all inlined
2023-03-22 17:11:41 -04:00
illua1
b0045cc797 Fix #106002: crash when passing field into Switch node when it doesn't support it
Pull Request: https://projects.blender.org/blender/blender/pulls/106006
2023-03-22 17:54:27 +01:00
Sergey Sharybin
ad96939a66 Fix warning in points to volume node
Strict compiler was generating possible-uninitialized warning.

Added an explicit initialization and asserts to solve the noisy
output and catch possible errors early on.

Pull Request: https://projects.blender.org/blender/blender/pulls/105990
2023-03-22 12:35:19 +01:00
Alexander Revkov
a1f52a02a8 Mesh: Set bounds eagerly for spherical and cylindrical primitive nodes
Eager bounds calculation for cylindrical and spherical primitive nodes,
implemented in constant time rather than as a loop over all positions.
Takes into account the segments count of the circle from which they
are constructed. Solution of the task #105551.

Pull Request: https://projects.blender.org/blender/blender/pulls/105743
2023-03-21 22:38:40 -04:00
Omar Emara
04d128f545 Fix: Realtime compositor outputs wrong EXR passes
The Image node in the realtime compositor will output wrong passes if multiple passes are used at the same time. This is because images can only store a single layer, pass, and view at a time. Furthermore, they rely on operators and RNA callbacks to invalidate the cache when a different layer, pass, or view is requested. In effect, when the image node requests multiple passes at the same time without any kind of cache invalidation, wrong passes are output.

While a proper fix is to allow caching of multiple layers, passes, and views at the same time. This patch implements a temporary workaround by explicitly ensuring an up-to-date cache through a call to BKE_image_ensure_gpu_texture() when before requesting a GPU texture.

Pull Request: https://projects.blender.org/blender/blender/pulls/105547
2023-03-21 14:57:12 +01:00
Hans Goudey
309553fc07 BLI: Simplify and extend OffsetIndices class
Add `index_range()` and `is_empty()` functions, rename `ranges_num()`
to `size()` (clarifying the final extra integer as an implementation
detail). Also remove the `size(index)` function which gave almost the
same assembly as `[index].size()` (https://godbolt.org/z/PYzqYs3Kr).
2023-03-20 13:34:14 -04:00
Hans Goudey
16fbadde36 Mesh: Replace MLoop struct with generic attributes
Implements #102359.

Split the `MLoop` struct into two separate integer arrays called
`corner_verts` and `corner_edges`, referring to the vertex each corner
is attached to and the next edge around the face at each corner. These
arrays can be sliced to give access to the edges or vertices in a face.
Then they are often referred to as "poly_verts" or "poly_edges".

The main benefits are halving the necessary memory bandwidth when only
one array is used and simplifications from using regular integer indices
instead of a special-purpose struct.

The commit also starts a renaming from "loop" to "corner" in mesh code.

Like the other mesh struct of array refactors, forward compatibility is
kept by writing files with the older format. This will be done until 4.0
to ease the transition process.

Looking at a small portion of the patch should give a good impression
for the rest of the changes. I tried to make the changes as small as
possible so it's easy to tell the correctness from the diff. Though I
found Blender developers have been very inventive over the last decade
when finding different ways to loop over the corners in a face.

For performance, nearly every piece of code that deals with `Mesh` is
slightly impacted. Any algorithm that is memory bottle-necked should
see an improvement. For example, here is a comparison of interpolating
a vertex float attribute to face corners (Ryzen 3700x):

**Before** (Average: 3.7 ms, Min: 3.4 ms)
```
threading::parallel_for(loops.index_range(), 4096, [&](IndexRange range) {
  for (const int64_t i : range) {
    dst[i] = src[loops[i].v];
  }
});
```

**After** (Average: 2.9 ms, Min: 2.6 ms)
```
array_utils::gather(src, corner_verts, dst);
```

That's an improvement of 28% to the average timings, and it's also a
simplification, since an index-based routine can be used instead.
For more examples using the new arrays, see the design task.

Pull Request: https://projects.blender.org/blender/blender/pulls/104424
2023-03-20 15:55:13 +01:00
Erik Abrahamsson
4b30b5c57f Nodes: SDF Volume nodes milestone 1
Geometry Nodes: SDF Volume nodes milestone 1

Adds initial support for SDF volume creation and manipulation.
`SDF volume` is Blender's name of an OpenVDB grid of type Level Set.
See the discussion about naming in #91668.

The new nodes are:
- Mesh to SDF Volume: Converts a mesh to an SDF Volume
- Points to SDF Volume: Converts points to an SDF Volume
- Mean Filter SDF Volume: Applies a Mean Filter to an SDF
- Offset SDF Volume: Applies an offset to an SDF
- SDF Volume Sphere: Creates an SDF Volume in the shape of a sphere

For now an experimental option `New Volume Nodes` needs to be
enabled in Blender preferences for the nodes to be visible.

See the current work plan for Volume Nodes in #103248.

Pull Request: https://projects.blender.org/blender/blender/pulls/105090
2023-03-19 11:21:08 +01:00
Richard Antalik
b6caf5980e Merge branch 'blender-v3.5-release' 2023-03-16 04:47:15 +01:00
Damien Picard
d7a68a9de8 Fix: Correct several messages in the UI
- Show/hide mask layers: the tooltip was confusing from a user's
  perspective, because they should not be expected to know what a hide
  flag is.
- Active Spline -> Active Point: likely a copy and paste error.
- Geo Nodes face is planar node: forgotten article.
- Axes, plural instead of axis for the viewport preferences. Here
  there are several usages of axis or axes. Since they refer to
  coordinate axes I believe they should be plural, even though the
  property `mini_axis_type` is still wrong.

-----
![image](/attachments/7ccdc72d-1e83-4c22-b8e9-c3b7c5fcf629)
![image](/attachments/746015bc-23e1-49bc-8828-df07d7605048)
![image](/attachments/61c58766-a8f5-4a90-b862-c9b57c44c355)

Pull Request: https://projects.blender.org/blender/blender/pulls/105814
2023-03-16 00:12:29 +01:00
Miguel Pozo
1fc892744c GPU: Fix: Use 2 slots for each UDIMs texture instead of 4
Create GPUNodeLinks for tiled_image and tiled_image_mapping together, to ensure they are the same texture.

See https://projects.blender.org/blender/blender/issues/105661#issuecomment-900351 for context and a more in-depth explanation.

Pull Request: https://projects.blender.org/blender/blender/pulls/105772
2023-03-15 17:58:25 +01:00
Clément Foucault
f518591abc Merge branch 'blender-v3.5-release' 2023-03-15 17:42:00 +01:00
Clément Foucault
fee6238d33 Fix #105323: Compositor: Crash when using color-ramp with b-spline
... or carnidal interpolation with only 2 color stops.

This was triggering an incorrect path due to missing optimisation cases.
Just fall back to the unoptimized case fixes the assert.
2023-03-15 17:30:29 +01:00