Compare commits

...

207 Commits

Author SHA1 Message Date
73ed9afd03 tag active viewer 2021-06-02 15:22:29 +02:00
de040ccb96 Merge branch 'master' into temp-geometry-nodes-viewer-node 2021-06-02 14:34:28 +02:00
6cd64f8caa Add workaround for gcc 11 compiler bug
Differential Revision: https://developer.blender.org/D11462
2021-06-02 14:13:55 +02:00
f92f5d1ac6 Keymap: use D-Key for view-pie menu
Use the D-key to access the view menu instead of the Tilda key,
which isn't accessible on some international layouts.

To resolve the conflicts the following changes have been made.

- `D` (motion) opens the view menu.
- `D` (mouse-button) uses grease-pencil (as it does currently).
- `Tilda` is used to for "Object Mode Transfer" instead of the View menu.

See T88092 for details.

Reviewed By: Severin

Ref D11189
2021-06-02 21:34:30 +10:00
69e15eb1e4 Object: support running transfer mode in any object mode
There is no need to limit this to sculpt mode,
prepare for key short cut changes, see: T88092.
2021-06-02 21:29:15 +10:00
facc62d0d5 Docs: 2.93 release description for Linux appdata 2021-06-02 13:22:16 +02:00
a9ba86c046 Merge branch 'master' into temp-geometry-nodes-viewer-node 2021-06-02 12:17:08 +02:00
46447594de Fix T88567: Cryptomatte only works for the first View Layer.
The view layer was always set to 0. This patch increments it.
2021-06-02 10:05:39 +02:00
507c19c0f7 Docs: formalize naming for generic callbacks in BKE_callbacks.h
Add a doc-string explaining the purpose of each call back and
how they should be used.

Also add a currently unused callback 'POST_FAIL' that is to be used in
cases the action fails - giving script authors, a guarantee that a
call to `pre` will always have a matching `post/post_fail` call.

- D11422: adds a callback that can use 'post_fail'.
- T88696: proposed these conventions.
2021-06-02 17:35:24 +10:00
3b3742c75f Cleanup: trailing commas to avoid right shift
This matches most declarations already in this file.
2021-06-02 17:06:12 +10:00
b5a883fef9 Cleanup: spelling in comments 2021-06-02 17:04:57 +10:00
711ddea60e Fix assert with geometry node output
The previous commit (my own) returned early without providing a value
for the node's output geometry set, which is required.
2021-06-01 23:50:23 -04:00
6b5bbd22d9 Cleanup: Avoid duplicating node input retrieval
Pass the selection name and the invert argument to each component
instead of retrieving them every time.
2021-06-01 17:51:21 -04:00
Wannes Malfait
464797078d Geometry Nodes: Add Delete Geometry Node
This node is similar to the mask modifier, but it deletes the elements
of the geometry corresponding to the selection, which is retrieved as
a boolean attribute. The node currently supports both mesh and point
cloud data. For meshes, which elements are deleted depends on the
domain of the input selection attribute, just like how behavior depends
on the selection mode in mesh edit mode.

In the future this node will support curve data, and ideally volume
data in some way.

Differential Revision: https://developer.blender.org/D10748
2021-06-01 17:32:03 -04:00
3400ba329e VSE: Use own category for metadata panel
Metadata panel was visible in each category. In other editors, this
panel is usually placed in category with other source media properties.
In sequencer, metadata is transfered over while compositing and relation
to particular strip is lost, therefore separate category for metadata
seems to be best option. Since Metadata panel is alone in this category,
it will be open by default.
2021-06-01 23:06:31 +02:00
17b09b509c Geometry Nodes: Skip calculating normals in transform node
This commit skips the eager recalculation of mesh normals in the
transform node. Often another deformation or topology-altering
operation will happen after the transform node, which means the
recalculation was redundant anyway.

In one of my test cases this made the node more than 14x faster.
Though depending on the situation the cost of updating the normals
may just be shifted elsewhere.
2021-06-01 12:03:59 -04:00
5b6e0bad1b Fix T88715: particle size influence texture not working for 'keyed' or 'none' physics types
This was reported for the special case of mapping with "Strand /
Particle" coords, but was not working with other coordinates either.

Dont see a reason for not supporting Size influence textures for these
kinds of particles (and since these types of particles have an "age"
like all others as well, even the "Strand / Particle" coords are
supported here as well)

Maniphest Tasks: T88715

Differential Revision: https://developer.blender.org/D11449
2021-06-01 17:54:20 +02:00
404b946ac0 LibOverride: Fix again infinite loop in resync in some complex/degenerated cases.
Broken in recent refactor of (recursive)resync, reported by studio,
thanks.
2021-06-01 17:37:22 +02:00
842375e1ed test adding shortcut for geo viewer 2021-06-01 16:23:43 +02:00
41bfb64da1 Merge branch 'master' into temp-geometry-nodes-viewer-node 2021-06-01 16:05:30 +02:00
6583fb67c6 Geometry Nodes: add empty material slot to new meshes
This fixes T88455 by adding an empty material slot to newly
generated meshes. This allows the object to overwrite the
"default" material without any extra nodes. Technically,
all polygons reference the material index 0 already, so it
makes sense to add a material slot for this material index.

Differential Revision: https://developer.blender.org/D11439
2021-06-01 15:25:01 +02:00
James Monteath
8a63466ca3 BuildBot: Cleanup
Removing scripts that were placed in the source tree that would drive
the old buildbot. With the new buildbot in place these scripts aren't
being used anymore.

The buildbit is currently driven by
`build_files/config/pipeline_config.json`. In the near future make
update would also use this config.

Overview of the new buildbot: https://wiki.blender.org/wiki/Infrastructure/BuildBot

Work done by James Monteath.
2021-06-01 15:18:11 +02:00
db59a5f9f0 Merge branch 'master' into temp-geometry-nodes-viewer-node 2021-06-01 13:54:07 +02:00
6899dcab53 Fix T88658: Force Fields of curve shape can crash if curve has only one point
`bvhtree_from_mesh_edges_create_tree` can actually leave the BVHTree
NULL (e.g. if no edges are present).

Now dont allocate `BVHTreeFromMesh` on the `SurfaceModifierData` at all
in case the tree would be NULL anyways.
Places like `get_effector_data` check for `SurfaceModifierData`-
>`BVHTreeFromMesh` and dont try to stuff like getting a closest point on
surface, which would crash as soon as BVHNodes would need to be accessed
(from the NULL BVHTree).

Maniphest Tasks: T88658

Differential Revision: https://developer.blender.org/D11443
2021-06-01 13:38:29 +02:00
c078540512 Cleanup: remove unused parameter. 2021-06-01 13:18:41 +02:00
6f1af44695 Cleanup: remove unused parameter. 2021-06-01 13:18:41 +02:00
633b70905a Cleanup: NULL->nullptr. 2021-06-01 13:18:41 +02:00
f0d93a71df Cleanup: API for MeshExtractRunTimeData. 2021-06-01 13:18:41 +02:00
98c6626729 DrawManager: Use CPP for Mesh Extraction Scheduling.
More cleanups will come to make this more CPP-like.
2021-06-01 13:18:41 +02:00
4b57bbac22 Cleanup: LibOverride: rename 'local' group tagging functions to 'overrides'.
Since we use them on linked data now as well, 'local' does not fit them
anymore.
2021-06-01 12:29:56 +02:00
bf8659930f Fix libOverride resync issues in some corner-cases.
This commit fixes two different issues:
* In some cases, when an object was added to a sub-collection and used
  into a different subcollection, and the root common collection would
  not need to be resynced, it would end up creating multiple overrides
  of the new object. This was affecting both normal and recursive
  resync.
* In recurisve resync case, the barrier code to define what is part or
  not of a override group/hierarchy was wrong.

Note that the current solution for the first issue is sub-optimal (it
goes back to the root of the override hierarchy and resync the whole
thing), a better solution is TODO for now.
2021-06-01 12:01:06 +02:00
f25316e97a IDManagement: BKE_libblock_rename: assert we get local ID.
For now at least, linked data should never be renamed that way.
2021-06-01 12:01:06 +02:00
84e16c4992 Fix BLI_libblock_ensure_unique_name not ignoring linked IDs.
This function would considere that there was a name conflict even in
case existing ID would be a linked one.

This is only a (symbolic) perforance improvement and logical fix, since
`BKE_id_new_name_validate` would not do that mistake anyway.
2021-06-01 12:01:06 +02:00
db15c9d1bd ID Management: Allow unique name check for linked IDs too.
This is mandatory for liboverride resync, since this feature may imply
we have to create linked overrides in libraries, and there may be
several copies of those.

This is also a first step to a more general support of IDmanagement-editing
library data.

Note that this commit should have absolutely no effect on current code,
as the only function allowed to check unique names for linked IDs
currently is `BKE_libblock_management_main_add`, which is unused.

This commit also adds some basic testing for `BKE_id_new_name_validate`.
2021-06-01 12:01:06 +02:00
27910ccce6 Cleanup: clang-tidy
* `readability-redundant-member-init`
* `readability-inconsistent-declaration-parameter-name`
* Remove constructor that can be defaulted.
2021-06-01 12:00:16 +02:00
9adfd278f7 Compositor: Full-frame base system
This patch adds the base code needed to make the full-frame system work for both current tiled/per-pixel implementation of operations and full-frame.

Two execution models:
- Tiled: Current implementation. Renders execution groups in tiles from outputs to input. Not all operations are buffered. Runs the tiled/per-pixel implementation.
- FullFrame: All operations are buffered. Fully renders operations from inputs to outputs. Runs full-frame implementation of operations if available otherwise the current tiled/per-pixel. Creates output buffers on first read and free them as soon as all its readers have finished, reducing peak memory usage of complex/long trees. Operations are multi-threaded but do not run in parallel as Tiled (will be done in another patch).

This should allow us to convert operations to full-frame in small steps with the system already working and solve the problem of high memory usage.

FullFrame breaking changes respect Tiled system, mainly:
- Translate, Rotate, Scale, and Transform take effect immediately instead of next buffered operation.
- Any sampling is always done over inputs instead of last buffered operation.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D11113
2021-06-01 10:51:53 +02:00
930ad9257d Cleanup: Split draw_cache_extract_mesh into multiple files.
draw_cache_extract_mesh for task scheduling. Will be refactored to draw_cache_extract_mesh_scheduling later on after migrating to CPP.

draw_cache_extract_mesh_render_data extraction of mesh render data from edit mesh/mesh into a more generic structure.

draw_cache_extract_mesh_extractors containing all the extractors. This will be split up further into a single file per extractor.
2021-06-01 09:23:37 +02:00
13deb5088a Cleanup: split face tessellation into inline functions
Prepare for multiple code-paths that recalculate tessellation.
2021-06-01 15:30:53 +10:00
b8d0f28f70 Cleanup: split bmesh tessellation into it's own file
Prepare for further refactoring for these functions.
2021-06-01 14:04:00 +10:00
3a18e304be Cleanup: remove disabled face tessellation logic
This was kept since these blocks are easier to follow.
Remove as the overall result wasn't so readable
(especially with nested ifdef's).

Replace disabled code with comment on the indices used for quads/tris.
2021-06-01 13:26:26 +10:00
5e7fb77dc4 BMesh: remove checks for tessellating 2 sided faces
2 sided faces aren't supported and will cause problems in many areas
of Blender's code.

Removing (implied) support for faces with fewer than 3 sides
means the total number of triangles is known ahead of time.

This simplifies adding support for multi-threading and partial updates
to an existing tessellation - as the face and loop indices can be used
to access the range of triangles associated with a face.

Also correct outdated comments.
2021-06-01 13:25:00 +10:00
f8ce744c83 Cleanup: correct sculpt quat argument size 2021-06-01 12:50:01 +10:00
f71cf99616 GPU: add 2D projection function
When projecting into screen space Z value isn't always needed.
Add 2D projection functions, renaming them to avoid accidents
happening again.

- Add GPU_matrix_project_2fv
- Add ED_view3d_project_v2
- Rename ED_view3d_project to ED_view3d_project_v3
- Use the 2D versions of these functions when the Z value isn't used.
2021-06-01 12:49:53 +10:00
c145cb7998 Fix buffer overrun in paint_line_strokes_spacing
Error in 87cafe92ce
2021-06-01 12:49:20 +10:00
e7b8a3cb0a Cleanup: spelling in comments 2021-06-01 12:49:18 +10:00
c59d2c739d Cleanup: spelling in comments 2021-06-01 01:45:17 +02:00
f253e59221 Docs: Limit the OCIO env vars that we document
Brecht mentioned that these are a bit obscure and don't make much sense 
to override these.
2021-05-31 19:19:00 -04:00
8180d478e1 Speedup exact boolean by avoiding some mallocs and frees.
This is from patch D11432 from Erik Abrahamsson. He found that
in some mpq3 functions called frequently from loops, passing in
buffers for termporary mpq3 values can save substantial time.
On my machine, his example in that patch went from 9.48s to 7.50s
for the boolean part of the calculation. On his machine, a running
time went from 17s to 10.3s.
2021-05-31 17:03:48 -04:00
73967e2047 Fix undeclared identifiers with 'DEBUG_TIME'
These identifiers were accidentally removed in rB44d2479dc36f.
2021-05-31 16:28:14 -03:00
09fb81f66d Merge branch 'blender-v2.93-release' 2021-05-31 19:23:49 +02:00
25316ef9d7 Cycles: optimize 3D viewport rendering with camera passepartout
If the area outside the camera is fully opaque, don't render it.

Contributed by Kdaf.

Differential Revision: https://developer.blender.org/D11182
2021-05-31 19:23:44 +02:00
875a8a6c79 Cleanup: Replace fseek() calls with BLI_fseek()
The fseek() function on Windows only accepts a 32-bit long offset
argument. Because of this we have our own version, BLI_fseek(), which
will use 64-bit _fseeki64() on Windows. This patch just replaces some
fseek() calls with BLI_fseek().

Differential Revision: https://developer.blender.org/D11430

Reviewed by Brecht Van Lommel
2021-05-31 10:08:58 -07:00
James Monteath
ee54a8ace7 Update all README to clearify intention or usage
Add snap configuration file used by Buildbot snap store steps1
2021-05-31 18:58:42 +02:00
261a10edb0 Display source video fps in the VSE
Now FPS is displayed in the video source for videos to provide easy
access.

Reviewed By: Richard Antalik

Differential Revision: http://developer.blender.org/D11441
2021-05-31 18:22:24 +02:00
d647e730fb Win: Fix warnings as errors being off for bmesh
bf_bmesh historically always build with the /WX flag
on windows making all warnings errors, somewhere along
the way this has broken for msbuild, ninja still exhibits
the expected behaviour.

The flags are still passed to the target, and I've validated
they are there when the add_library call fires, but they
somehow never make it to the generated msbuild project files.

I suspect this is a cmake bug but I'm seemingly unable
to extract a repro case to file a bug upstream.

Setting the same options target_compile_options seems to work,
I'm not happy about the unexplained nature of the breakage
but this will have to do for now.
2021-05-31 09:59:29 -06:00
46a14bd6a3 Fix T88670: Load Previous Settings does not copy symlinks
The same code existed in 2.82 and earlier so this should be safe. Removing the
custom implementation of shutil.copytree in f34d5d9 did not correctly add back
the option to copy symlinks.
2021-05-31 17:30:04 +02:00
b862916eaf VSE: Fix missing cache invalidation
Fixes T88606
2021-05-31 17:28:49 +02:00
Jeroen Bakker
44d2479dc3 Refactor: DRW Mesh Extractor: Join the extractors in a same loop
This patch replaces / redoes the entire MeshExtractors system.
Although they were useful and facilitated the addition of new buffers, they made it difficult to control the threads and added a lot of threading overhead.

Part of the problem was in traversing the same loop type in different threads. The concurrent access of the BMesh Elements slowed the reading.

This patch simplifies the use of threads by merging all the old callbacks from the extracts into a single series of iteration functions.

The type of extraction can be chosen using flags.

This optimized the process by around 34%.

Initial idea and implementation By @mano-wii.
Fine-tuning, cleanup by @atmind.

MASTER:
large_mesh_editing:
- rdata 9ms iter 50ms (frame 155ms)
- Average: 6.462874 FPS

PATCH:
large_mesh_editing:
- rdata 9ms iter 34ms (frame 136ms)
- Average: 7.379491 FPS

Differential Revision: https://developer.blender.org/D11425
2021-05-31 17:11:25 +02:00
aebeb85fe0 Windows: Clean-up win 8/8.1 API use
For 2.93 we bumped the minimum windows requirement
to windows 8.1, but did not do any clean-up of any
win 8/8.1 API usage we dynamically accessed though
LoadLibrary/GetProcAddress.

This patch bumps _WIN32_WINNT to 0x0603 (win 8.1)
and cleans up any API use that was accessed in a
more convoluted way than necessary

Differential Revision: https://developer.blender.org/D11331

Reviewed by: harley, nicholas_rishel
2021-05-31 08:56:57 -06:00
d67c13ca76 Cleanup: else-after-return 2021-05-31 15:48:06 +02:00
d94ba979d8 Fix T88569: UI VSE: Menu-based range change, doesn't update the Timeline scrollbar width
Use the appropriate notifier, listeners were already doing the rest
properly.

Maniphest Tasks: T88569

Differential Revision: https://developer.blender.org/D11436
2021-05-31 15:32:33 +02:00
a25dbf243a remove outdated code 2021-05-31 15:06:11 +02:00
e0a1c3da46 Fix T88666: Cryptomatte: EXR sequence does not update when scrubbing the timeline.
Cause is that initializing the cryptomatte session would reset the
current frame of an image sequence. The solution is to always use the
scene current frame so it resets to the correct frame.

This was a todo that wasn't solved after it landed in master.
Needs to be backported to 2.93.
2021-05-31 14:32:39 +02:00
782b0df9c8 recalc when viewer node changed 2021-05-31 13:49:13 +02:00
8969f4ba23 take viewer node into account when searching for output connection 2021-05-31 13:44:15 +02:00
7cea4dc6f5 compute viewed sockets even when it is not required for the output 2021-05-31 13:35:29 +02:00
1e006e58dc remove toggles from node headers 2021-05-31 13:18:35 +02:00
0702871110 handle viewer node in modifier 2021-05-31 13:15:41 +02:00
5ea958a992 update spreadsheet context when activating viewer 2021-05-31 13:08:20 +02:00
7694a098ef initial viewer node 2021-05-31 12:46:39 +02:00
e9f2f17e85 Fix (unreported): TextureOperation inputs have no resolution
When compositor node tree has a texture node, TextureOperation vector inputs  has always {0, 0} resolution instead of having same resolution as TextureOperation which is the expected behaviour for resolutions propagation.

Current TextureOperation determineResolution implementation doesn't determine inputs resolution, breaking propagation of preferred resolution and that's the reason why they are always 0. Setting scene resolution always would mean it is its own resolution and could make sense, but setting it only when preferred resolution is 0, breaks preferred resolution logic affecting other operations as explained in D10972. In any case scene resolution is already the default preferred resolution on viewer and compositor nodes.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D11381
2021-05-31 12:26:46 +02:00
83fe479c7f Merge branch 'blender-v2.93-release' 2021-05-31 11:56:17 +02:00
ce649c7344 Fix T88623, T87044: Make encoded videos play correctly in VLC
The issue was two fold. We didn't properly:

1. Initialize the codec default values which would lead to VLC
   complaining because of garbage/wrong codec settings.

2.Calculate the time base for the video. FFmpeg would happily accept
  this but VLC seems to assume the time base value is at least somewhat
  correct and couldn't properly display the frames as the internal time
  base was huge. We are talking about 90k ticks (tbn) for one second of
  video!

This patch initializes all codecs to use their default values and fixes
the time base calculation so it follows the guidelines from ffmpeg.

Reviewed By: Sergey, Richard Antalik

Differential Revision: http://developer.blender.org/D11426
2021-05-31 11:29:08 +02:00
2161840d07 Fix (studio-reported) crash in collection management code.
Code checking for potential collection loop dependencies can be called
in cases where we cannot guarantee that there is no NULL pointers, so we
need to check those. Was already done for objects.

NOTE: doubled-checked by @jbakker, thanks.
2021-05-31 11:25:08 +02:00
2534609262 Revert "Added v2.93 pipeline config for new buildbot."
This reverts commit 632bfee0a5.
This config is only intended for 2.93. Master will get its own config
file after testing that 2.93 is correct.
2021-05-31 11:16:13 +02:00
86eaddb3ca Merge branch 'blender-v2.93-release' 2021-05-31 11:15:50 +02:00
632bfee0a5 Added v2.93 pipeline config for new buildbot. 2021-05-31 11:15:03 +02:00
26fb7b9474 Geometry Nodes: do not create unnecessary geometry components
Previously, making instances real would always create an (empty)
volume and curve component, even when not necessary.

This also fixes T88653.
2021-05-31 11:12:39 +02:00
421c0b45e5 Fix (studio-reported) crash in collection management code.
Code checking for potential collection loop dependencies can be called
in cases where we cannot guarantee that there is no NULL pointers, so we
need to check those. Was already done for objects.
2021-05-31 10:20:23 +02:00
Jeroen Bakker
5f749a03ca Fix T88456: DrawManager: Keep subset RenderMeshData around when geometry does not change.
Reuse loose geometry during selection (and other operations) from
previous calculation. Loose geometry stays the same, but was
recalculated to determine the size of GPU buffers. This patch would
reuse the previous loose geometry when geometry wasn't changed.

Although not the main bottleneck during selection it is measurable.

Master.
`rdata 46ms iter 55ms (frame 410ms)`

This patch.
`rdata 5ms iter 52ms (frame 342ms)`

Reviewed By: mano-wii

Differential Revision: https://developer.blender.org/D11339
2021-05-31 09:33:31 +02:00
a1556fa05c Boolean: applying patch D11431 to speed up hole-tolerant raycast.
This patch from Erik Abrahamsson uses a parallel_for to speed up
the case where the input is not manifold and the "hole_tolerant"
option is set.
In a test case on a 24 core (48 thread) machine, this sped up a
the boolean part on an object with 221k triangles from 12.06s to 0.46s.
2021-05-30 16:37:49 -04:00
e6a69f7653 Docs: Capitalize first word of sentence 2021-05-30 11:09:01 -04:00
7b5796dcaa Docs: clarify description and usage of 'bpy.app.version_file'
Fixes T88669
2021-05-30 11:07:38 -04:00
f5d14e36e8 PyDoc: Use em dash instead of comma for enum items 2021-05-29 12:16:13 -04:00
870aaf3fb6 Docs: Add documentation for 'material_index'
Fixes T88485
2021-05-29 11:22:58 -04:00
6f86d50b92 UI: Match tooltip with interface name 2021-05-29 11:22:58 -04:00
30f7acfffa Docs: Add relevant OCIO envvars to Blender's help message 2021-05-29 11:22:58 -04:00
115547991a Merge branch 'blender-v2.93-release' 2021-05-29 15:29:42 +02:00
3c02e648f3 GPencil: Fix unreported random rotation for single point with texture
When using ``Path`` alignment, if the stroke has one point the texture rotates randomly when move the viewport. This was because with one point is impossible to calculate a path.

Now, if the stroke has only one point, the texture for this stroke is aligned to Object.
2021-05-29 15:29:08 +02:00
James Monteath
ffe7a41540 Fix typos 2021-05-28 20:50:52 +02:00
James Monteath
74c7e21f6c Add and update README.md files for CI script removal 2021-05-28 19:46:53 +02:00
9225fe933a Make encoded video fps correct with ffmpeg < 4.4
Before the FFmpeg commit: github.com/FFmpeg/FFmpeg/commit/1c0885334dda9ee8652e60c586fa2e3674056586
FFmpeg would use deprecated variables to calculate the video fps.

We don't use these deprecated variables anymore, so ensure that the
duration is correct in ffmpeg versions without this fix.

Reviewed By: Sergey, Richard Antalik

Differential Revision: http://developer.blender.org/D11417
2021-05-28 18:37:36 +02:00
3311350670 Fix T87932: Failure to build movie strip proxy
We didn't initialize the scaled proxy frame properly.
This would lead to issues in ffmpeg 4.4 as they are more strict that the API is properly used.

Now we initialize the size and format of the frame.
2021-05-28 18:35:26 +02:00
5b8a41d387 Merge branch 'blender-v2.93-release' 2021-05-28 18:19:07 +02:00
653bbaa246 Geometry Nodes: Polish switch node UI
Based on the task T88006, there are a few simple changes
to make to improve the switch node:
- Change the label to "False" / "True" for clarity
- Change default to geometry, as it's the basic data container in
  geometry nodes.
- Change node class to `NODE_CLASS_CONVERTOR`, which was an oversight
  in the original patch.

I will add the new socket types (material and texture) in a separate commit.
Thanks to @EitanSomething for the original patch.

Differential Revision: https://developer.blender.org/D11165
2021-05-28 12:17:04 -04:00
c369382977 EEVEE: Fix NaN caused by ensure_valid_reflection()
This was caused by unsafe sqrt calls.

Fixes T86578 white artifacts in EEVEE

Reviewed By: brecht, dfelinto

Differential Revision: https://developer.blender.org/D11428
2021-05-28 18:16:56 +02:00
c0ce7fce89 Merge branch 'blender-v2.93-release' 2021-05-28 12:09:08 -04:00
20e250dae3 Fix T88601: Attribute Compare boolean doesn't expose socket
While we could make this node work for boolean inputs in the future,
currently it's really just designed to compare "float-like" inputs.
Many comparison modes don't even make sense for boolean inputs.
Therefore, the simplest fix for this bug is just to disable the
boolean attribute input modes for this node.

Differential Revision: https://developer.blender.org/D11427
2021-05-28 12:04:52 -04:00
James Monteath
fc2b56e68c Add branch based pipeline config file used by buildbot and make_update.py script. 2021-05-28 17:53:02 +02:00
a4700549e0 GPencil: Fix unreported random rotation for single point with texture
When using ``Path`` alignment, if the stroke has one point the texture rotates randomly when move the viewport. This was because with one point is impossible to calculate a path.

Now, if the stroke has only one point, the texture for this stroke is aligned to Object.
2021-05-28 17:15:44 +02:00
c65f4b3d76 DrawManager: Early return for buffer cache creation
No real functional changes.

This is useful for benchmark cases when `cache->uv_cage` is passed but
has no buffers are requested.
2021-05-28 11:51:13 -03:00
11e32332dd Geometry Nodes: Add Mesh to Curve Node
This node creates poly curve splines from mesh edges. A selection
attribute input allows only using some of the edges from the mesh.
The node builds cyclic splines from branchless groups of edges where
possible, but when there is a three-way intersection, the spline stops.

The node also transfers all attributes from the mesh to the resulting
control points. In the future we could add a way to limit that to a
subset of the attributes to improve performance.

The algorithm is from Animation Nodes, written by @OmarSquircleArt.
I added the ability to use a selection, attribute transferring, and
used different variable names, etc, but other than that the algorithm
is the same.

Differential Revision: https://developer.blender.org/D11265
2021-05-28 10:42:22 -04:00
080623b8ac Fix incorrect Denoise node SSE 4.1 warning on macOS Intel 2021-05-28 16:02:43 +02:00
418888f1c9 MSVC: Fix build error with 16.10/11
Not entirely sure why this was not an issue for 16.9
but TBB includes the Windows.h header which by default
will define min and max macro's

These collide with the stl versions in <algorithm>

This patch requests Windows.h not to define the
problematic macro's, resolving the conflict.
2021-05-28 07:57:21 -06:00
bdac47f8d4 Merge branch 'blender-v2.93-release' 2021-05-28 15:47:38 +02:00
adafd7257d Fix T88635: VSE: Select Linked gives unpredictable results
Caused by {rB66923031e6f2}.

Code would process unselected sequences and skip selected, needs to be
the other way around.

Maniphest Tasks: T88635

Differential Revision: https://developer.blender.org/D11424
2021-05-28 15:41:56 +02:00
97ccd592ce Fix crash in liboverride resync.
Reported by studio (@andy), thanks.
2021-05-28 15:33:04 +02:00
James Monteath
51bbdfbef3 Moved to new git repo 2021-05-28 12:16:45 +02:00
63e50cf265 Fix T88499: Copy data path operator does not consider library affiliation
When using the operator `ui.copy_data_path_button(full_path=True)` ({key
ctrl shift Alt C} on hover) the copied path does not consider the
library origin. That means that when there is a name clash the data path
is not accurate and refers to the local item instead.

This patch adds the library (if the ID is linked) of the returned string
from RNA_path_full_ID_py.

bpy.data.objects["Cube", "//library.blend"] instead of
bpy.data.objects["Cube"]

note: parsing this happens in
pyrna_prop_collection_subscript_str_lib_pair_ptr

Maniphest Tasks: T88499

Differential Revision: https://developer.blender.org/D11412
2021-05-28 11:06:55 +02:00
James Monteath
5baf1dddd5 Buildbot related files have been moved to own repository 2021-05-28 10:16:39 +02:00
3ec57ce6c7 Tests: add utility to generate interactive user actions
A utility that supports passing in actions as command line arguments for
writing reproducible interactions, benchmarking, profiling and testing.

Unlike regular scripts this is able to control model operators usefully.

Typical ways of controlling Blender using this utility are via
operator id's, menu search and explicit events.
Others methods can be added as needed.

See the doc-string for example usage.
2021-05-28 16:59:38 +10:00
3bee77bb7c Cleanup: use static set syntax 2021-05-28 16:58:50 +10:00
6b03621c01 DrawManager: Use Compute Shader to Update Hair.
This patch will use compute shaders to create the VBO for hair.
The previous implementation uses transform feedback.

Timings before: between 0.000069s and 0.000362s.
Timings after:  between 0.000032s and 0.000092s.

Speedup isn't noticeable by end-users. The patch is used to test
the new compute shader pipeline and integrate it with the draw
manager. Allowing EEVEE, Workbench and other draw engines to
use compute shaders with the introduction of `DRW_shgroup_call_compute`
and `DRW_shgroup_vertex_buffer`.

Future improvements are possible by generating the index buffer
of hair directly on the GPU.

NOTE: that compute shaders aren't supported by Apple and still use
the transform feedback workaround.

Reviewed By: fclem

Differential Revision: https://developer.blender.org/D11057
2021-05-28 08:16:26 +02:00
4a1ba155d5 Merge branch 'blender-v2.93-release' 2021-05-27 21:27:48 -04:00
02395fead7 Docs: Update RNA to User Manual mappings 2021-05-27 21:25:31 -04:00
0a83f32d79 Fix T86465: Annotation Tool is missing in VSE Preview toolbar
Added missing topbar in VSE.

Also added the Stabilizer options to Topbar for all modes.

Reviewed By: mendio, pepeland

Maniphest Tasks: T86465

Differential Revision: https://developer.blender.org/D11347
2021-05-27 19:53:23 +02:00
758c115103 Fix own crash in today's rBf68288a8746f. 2021-05-27 19:32:35 +02:00
311ffd967b LibOverride: refactor recursive resync.
We need to re-evaluate what needs to be resynced after each step of
processing overrides from a given 'indirect level' of libraries.
Otherwise, recusrive overrides (overrides of linked overrides) won't
work.

Note that this should not change too much in practice currently, since
there are other issues with recursive overrides yet.

Also, checks (CLOG errors) added show that some ID (node trees) seem to
be detected as needing resynced even after beig just resynced, this
needs further investigation still. Could be though that it is due to
limit currently set on nodetrees, those are always complicated
snowflakes to deal with...
2021-05-27 19:32:35 +02:00
530f2994e9 Fix T88614: Mixdown crashes Blender 2.92.0 and 3.0.0 Alpha
The problem is caused by the most recent ffmpeg version (4.4) which
needs channels to be set when submitting a frame for encoding.
2021-05-27 19:05:17 +02:00
dfa3dcbab9 Fix T88625: Multiobject UV hiding/unhiding does not work with UV_SYNC_SELECTION
Oversight in {rB470f17f21c06}.

Hiding was only done for the first mesh, then the operator finished (in
case of UV_SYNC_SELECTION).
Now just continue to the next.

Maniphest Tasks: T88625

Differential Revision: https://developer.blender.org/D11413
2021-05-27 17:27:38 +02:00
8590cb26a9 Merge branch 'blender-v2.93-release' 2021-05-27 17:15:39 +02:00
ba4228bcf7 Revert "EEVEE: Ensure Reflection: Use new implementation"
Both before and after can have artifacts with some normal maps, but this seems to give
worse artifacts on average which are not worth the minor performance increase.

This reverts commit 5c4d24e1fd.

Ref T88368, D10084
2021-05-27 17:12:23 +02:00
3025c34825 Geometry Nodes: Expose texture and material inputs to modifier
This allows choosing material and texture sockets for the group input
node in the modifier. Note that currently grease pencil materials are
displayed in the list, even though grease pencil data is not supported
yet by geometry nodes. That is more complicated to fix in this case,
since we use IDProperties to store the dynamic exposed inputs.

Differential Revision: https://developer.blender.org/D11393
2021-05-27 11:06:08 -04:00
24b2482ad9 Cleanup: Fix forward declaring class with "struct" 2021-05-27 11:00:00 -04:00
a3edf4a381 Fix build error: Make CurveEval a struct
We need a pointer to this in DNA, which means it cannot be a class.
2021-05-27 10:50:43 -04:00
e8ca635e43 Cleanup: rename blender-launcher source file.
blender-laucher.c was not an ideal name for this file
since it's not directly clear it is windows only.

This change renames it to blender_launcher_win32.c
to be more in line with other win32 specific files
we have.
2021-05-27 08:10:31 -06:00
ac833108db Geometry Nodes: Draw curve data in the viewport
This patch adds relatively small changes to the curve draw
cache implementation in order to draw the curve data in the
viewport. The dependency graph iterator is also modified
so that it iterates over the curve geometry component, which
is presented to users as `Curve` data with a pointer to the
`CurveEval`

The idea with the spline data type in geometry nodes is that
curve data itself is only the control points, and any evaluated
data with faces is a mesh. That is mostly expected elsewhere in
Blender anyway. This means it's only necessary to implement
wire edge drawing of `CurveEval` data.

Adding a `CurveEval` pointer to `Curve` is in line with changes
I'd like to make in the future like using `CurveEval` in more places
such as edit mode.

An alternate solution involves converting the curve wire data
to a mesh, however, that requires copying all of the data, and
since avoiding it is rather simple and is in-line with future plans
anyway, I think doing it this way is better.

Differential Revision: https://developer.blender.org/D11351
2021-05-27 10:08:40 -04:00
5621a8ed7f Fix T88452: Point Separate crash on curve component
The point separate node should create a point cloud from control points
in this case, but for now disable the node on curves to avoid the crash.
2021-05-27 09:37:19 -04:00
a12fd5c3ad Cleanup: Use consistent variable names 2021-05-27 09:27:08 -04:00
5721c89ba8 Cleanup: rename BKE_main_id_{clear_newpoins => newptr_and_tag_clear}
It wasn't obvious this function cleared the tag as well.
2021-05-27 22:44:02 +10:00
Erik Abrahamsson
9200e73136 Cleanup: remove duplicate LIB_TAG_NEW untag code
This patch removes unnecessary calls to `BKE_main_id_tag_all` where the
same job is done by `BKE_main_id_clear_newpoins` on the following line.

Reviewed By: campbellbarton, mont29

Ref D11379
2021-05-27 22:42:41 +10:00
6ad4b8b764 LineArt: List Optimization for tile linked data.
Use array instead of ListBase for line art
bounding area linked triangles and edges.

Reviewed By: Sebastian Parborg (zeddb)

Differential Revision: https://developer.blender.org/D11302
2021-05-27 20:33:02 +08:00
f45270b4fb Cleanup: Line art variable naming.
Change `reln` to `eln`.

Reviewed By: Sebastian Parborg (zeddb)

Differential Revision: https://developer.blender.org/D11411
2021-05-27 18:47:13 +08:00
42fba7f0d2 LibOverride: Add heuristic protection against infinite loop due to libraries inter-dependencies.
This is not supposed to happen, but better be safe than sorry, and
assume it is beyond unlikely that someone would use chains of over 10k
linked libraries.
2021-05-27 11:55:31 +02:00
ac2266fe57 Cleanup: Unused include in Cycles 2021-05-27 11:36:30 +02:00
2414a5787d Refactor: Move display pass to Cycles viewport parameters
Allows to centralize storage and modification checks in a single place,
avoiding duplication in the synchronization code.

Ideally we would somehow be able to more granularly modify Cycles side
objects. Leaving this for a future decision, because it might be better
to implement it as a graph on the sync side.
2021-05-27 11:36:30 +02:00
7b5f4c8837 Cleanup: Redundnat member init in Cycles viewport parameters 2021-05-27 11:36:30 +02:00
616b071c4f Refactor: Naming in Cycles viewport methods
Makes it more explicit they operate on shading/light.

Gives room to move more viewport related settings into this class and
cover with specific or generic modification checks.
2021-05-27 11:36:30 +02:00
15df83e9c9 Cleanup: Use logical OR in Cycles background shader 2021-05-27 11:36:30 +02:00
8f43e31a71 Cleanup: const qualifier of return type 2021-05-27 11:36:30 +02:00
4ea7742973 Refactor: Remove friend class from Cycles viewport parameters
Such pattern should only be used when it is really needed. Otherwise
just stick to a more regular design, without worrying who is the user
of the class. Otherwise it will be annoying to subclass or unit test.
2021-05-27 11:36:30 +02:00
bf7a67c5c6 Refactor: Rename pass accessor in viewport parameters
No need to state that it is a viewport display pass, since the method
is within viewport parameters it is implied that parameters do belong
to the viewport.

Brings this code closer to the Cycles-X branch.
2021-05-27 11:36:30 +02:00
06d48367fa Fix (studio-reported) infinite loop in resync code.
Very stupid mistake in libraries indirect-level building code, was not
skipping 'loop-back' ID pointers.

Note that we also need some level of checks for the case where there
would be an actual dependency loop between libraries, this is not
supposed to be possible, but better be safe than sorry. Will add in next
commit.
2021-05-27 10:49:31 +02:00
46e1ad711c Cleanup: inconsistent parameter name 2021-05-27 10:46:43 +02:00
ffa2a1771e Cleanup: inconsistent parameter name 2021-05-27 10:41:40 +02:00
fd0bb24e4a Cleanup: simplify logic for copying vector button as text
An arbitrary size offsets was used in float_array_to_string,
simplify the loop, use exact size limits.

Also rename variables so it's clear which array the length apply to.
2021-05-27 17:59:21 +10:00
41f2ea4045 Fix incorrect BLI_snprintf usage
Event though in practice this wasn't causing problems as the fixed size
buffers are generally large enough not to truncate text.

Using the result from `snprint` or `BLI_snprintf` to step over a fixed
size buffer allows for buffer overruns as the returned value is the size
needed to copy the entire string, not the number of bytes copied.

Building strings using this convention with multiple calls:

    ofs += BLI_snprintf(str + ofs, str_len_max - ofs);

.. caused the size argument to become negative,
wrapping it to a large value when cast to the unsigned argument.
2021-05-27 17:59:21 +10:00
1276d0024f Cleanup: specify array sizes, remove warnings in comments 2021-05-27 17:59:21 +10:00
f0342065b1 Cleanup: spelling 2021-05-27 17:59:21 +10:00
2ad3a1c318 Nodes: fix material node copied over when socket is copied
This was missing from rB207472930834a2916cf18bbdff51bcd77c6dd0c0.
2021-05-27 09:58:45 +02:00
223c6e1ead Geometry Nodes: disable multi-threading in evaluator for now
A deadlock could happen under certain circumstances when
geometry nodes is used on multiple objects.
Once T88598 is resolved, multi-threading can be enabled again.

Differential Revision: https://developer.blender.org/D11405
2021-05-27 09:43:11 +02:00
7d20cf92dd Cleanup: use UndoMesh as links instead of allocating LinkData
While the advantage isn't large,
it's simpler to skip the intermediate link.

Also remove unused next and previous struct members
from MeshUndoStep_Elem.
2021-05-27 16:44:21 +10:00
deb71cef38 Undo: resolve inefficient edit-mesh memory use with multiple objects
When editing more than 1 object at a time, complete copies of each mesh
were being stored. Now the most recent undo-data for each mesh is used
(when available).
2021-05-27 16:44:21 +10:00
a10e41b02e Cleanup: Remove completed "TODO" comment
Since rBb67fe05d4bea, the dependency graph supports relations
on collection geometry, and the nodes modifier uses that.
2021-05-26 22:32:53 -04:00
b132dd042e Cleanup: Simplify spline point attribute materialize functions
- Iterate over the mask directly instead of using an index.
- Use Span slice and copy_from instead of a lower level function.
2021-05-26 22:22:09 -04:00
c97b6215a3 Geometry Nodes: Support interpolation between curve domains
This commit adds interpolation from the point domain to the spline
domain and the other way around. Before this, spline domain attributes
were basically useless, but now they are quite helpful as a way to use
a shared value in a contiguous group of points.

I implementented a special virtual array for the spline to points
conversion, so that conversion should be close to the ideal performance
level, but there are a few ways we could optimize the point to spline
conversion in the future:
 - Use a function virtual array to mix the point values for each spline
   on demand.
 - Implement a special case for when the input virtual array is one of
   the virtual arrays from the spline point attributes. In other words,
   decrease curve attribute access overhead.

Differential Revision: https://developer.blender.org/D11376
2021-05-26 22:14:59 -04:00
f3944cf503 Win: Add launcher to hide the console window flash
This patch fixes a long-standing complaint from users:
the console window shortly flashing when they start
blender.

This is done by adding a new executable called
blender-launcher.exe which starts blender.exe while
hiding the console.

Any command line parameters given to blender-launcher
will be passed on to blender.exe so it'll be a drop
in replacement.

Starting blender.exe on its own will still function as
a proper console app so no changes required here for
users that use blender for batch processing.

Notable changes:

Registering blender (-R switch) will now register
blender-launcher as the preferred executable.

This patch updates the installer and updates the
shortcuts to start blender-launcher.exe rather
than blender.exe

Differential Revision: https://developer.blender.org/D11094

Reviewed by: brecht, harley
2021-05-26 20:02:35 -06:00
Germano Cavalcante
6fc9ec9257 Cleanup: Specify amount of buffers through preprocessor directives 2021-05-26 22:52:59 -03:00
e5b51cb511 Fix T88603: Crash with spline attributes after curve resample
The output curve's spline attribute domain custom data needs to be
reallocated with the correct length after adding the splines.
2021-05-26 21:12:38 -04:00
2c607ec2f6 Revert "DrawManager: Use Compute Shader to Update Hair."
This reverts commit 8f9599d17e.

Mac seems to have an error with this change.
```
                 ERROR: /Users/blender/git/blender-vdev/blender.git/source/blender/draw/intern/draw_hair.c:115:44: error: use of undeclared identifier 'shader_src'
                 ERROR: /Users/blender/git/blender-vdev/blender.git/source/blender/draw/intern/draw_hair.c:123:13: error: use of undeclared identifier 'shader_src'
                 ERROR: make[2]: *** [source/blender/draw/CMakeFiles/bf_draw.dir/intern/draw_hair.c.o] Error 1
                 ERROR: make[1]: *** [source/blender/draw/CMakeFiles/bf_draw.dir/all] Error 2
                 ERROR: make: *** [all] Error 2

```
2021-05-26 20:32:05 +02:00
7438f0c6c0 Cleanup: array-parameter warning with GCC 11
Pass the string size as this is less error prone in general.
2021-05-27 02:32:44 +10:00
c07c7957c6 Revert "Cycles: optimize ensure_valid_reflection(), reduces render time by about 1%"
Both before and after can have artifacts with some normal maps, but this seems to give
worse artifacts on average which are not worth the minor performance increase.

This reverts commit 21bc1a99ba.

Ref T88368, D10084
2021-05-26 18:27:21 +02:00
f12e2ec088 Fix buildbot CUDA/OptiX warnings on macOS
Explicitly disable these, rather than relying on them not being found.
Also, don't duplicates the architectures list.
2021-05-26 18:27:17 +02:00
de3d54eb9a GPencil: Cleanup - Conform with RNA naming scheme
The newly added `disable_masks_viewlayer` RNA property did not conform
with the RNA naming scheme. This renames it to `use_viewlayer_masks`.
2021-05-26 18:23:31 +02:00
df32b50344 Fix T88111: Skin modifier assets within invalid face normals
The skin modifier was moving vertices without updating normals for the
connected faces, this happened when smoothing and welding vertices.

Reviewed By: mont29

Ref D11397
2021-05-27 01:52:45 +10:00
6b00df1105 Cleanup: shadow warning
Move reproject_type into an extern, to avoid declaring multiple times.
2021-05-27 01:52:45 +10:00
72d660443b LibOverride: add recursive resync.
Recursive resync means also resyncing overrides that are linked from
other library files into current working file.

Note that this allows to get 'working' files even when their
dependencies are out of sync. However, since linked data is never
written/saved, this has to be re-done every time the working file is
loaded, until said dependencies are updated properly.

NOTE: This is still missing the 'report' side of things, which is part
of a larger task to enhance reports regarding both linking, and
liboverrides (see T88393).

----------

Technical notes:

Implementing this proved to be slightly more challenging than expected,
mainly because one of the key aspects of the feature was never done in
Blender before: manipulating, re-creating linked data.

This ended up moving the whole resync code to use temp IDs out of bmain,
which is better in the long run anyway (and more aligned with what we
generally want to do when manipulating temp ID data). It should also
give a marginal improvement in performances for regular resync.

This commit also had to carefully 'sort' libraries by level of indirect
usage, as we want to resync first the libraries that are the least directly
used, i.e. libraries that are most used by other libraries.
2021-05-26 17:05:01 +02:00
51ca9833d9 LibOverride: add helper to retrieve override data from an ID.
Embedded IDs do not own their own override data, but rather use the one
from their owner.
2021-05-26 17:05:01 +02:00
daf39af70a IDManagement: Shapekey: add a owner_get callback.
Even though shepkeys are not strictly speaking an embedded data, they
share quiet a few points with those, and from liboverride perspective
they are embedded, so...
2021-05-26 17:05:01 +02:00
653d39cec3 LibOverride: Do not try to generate override data of linked data.
This is obviously not saved, and should never be editable, so was only a
waste of time.
2021-05-26 17:05:01 +02:00
6cbe5dd1c3 ID management: remapping: add flag to enforce refcounting handling.
While indeally we should only skip refcounting when relevant tag is set,
doing this in remapping code is too risky for now.

Related to previous commit and T88555.
2021-05-26 17:05:01 +02:00
ee849ca0f8 ID management: Do not assume that NO_MAIN means NO_USER_REFCOUNT
While this is still very fuzzy in current code, this old behavior makes
it close to impossible to efficiently use out-of-main temp data, as it
implies that we'd need to update refcounts everytime we add something
back into BMain (an 'un-refcount' ID usages when removing from BMain).

Now that we have two separate flags/tags for those two different things,
let's not merge them anymore.

Note that this is somewhat on-going process, still needs more checks and
cleanup. Related to T88555.
2021-05-26 17:05:00 +02:00
Jeroen Bakker
8f9599d17e DrawManager: Use Compute Shader to Update Hair.
This patch will use compute shaders to create the VBO for hair.
The previous implementation uses tranform feedback.

Timings master (transform feedback with GPU_USAGE_STATIC between 0.000069s and 0.000362s
Timings transform feedback with GPU_USAGE_DEVICE_ONLY. between 0.000057s and 0.000122s
Timings compute shader between 0.000032 and 0.000092s

Future improvements:
* Generate hair Index buffer using compute shaders: currently done single threaded on CPU, easy to add as compute shader.

Reviewed By: fclem

Differential Revision: https://developer.blender.org/D11057
2021-05-26 17:03:37 +02:00
Jeroen Bakker
87055dc71b GPU: Compute Pipeline.
With the compute pipeline calculation can be offloaded to the GPU.
This patch only adds the framework for compute. So no changes for users at
this moment.

NOTE: As this is an OpenGL4.3 feature it must always have a fallback.

Use `GPU_compute_shader_support` to check if compute pipeline can be used.
Check `gpu_shader_compute*` test cases for usage.

This patch also adds support for shader storage buffer objects and device only
vertex/index buffers.

An alternative that had been discussed was adding this to the `GPUBatch`, this
was eventually not chosen as it would lead to more code when used as part of a
shading group. The idea is that we add an `eDRWCommandType` in the near
future.

Reviewed By: fclem

Differential Revision: https://developer.blender.org/D10913
2021-05-26 16:49:30 +02:00
e459a25e6c GPencil: Add option to disable masks in view layer
This patch adds an option in the Layers > Relations panel called "Disable Masks in Render".
When checked, no masks on this layer are included in the render.

Example:
| {F10087680} | {F10087681} |

See T88202 for why this is needed.

Reviewed By: antoniov

Maniphest Tasks: T88202

Differential Revision: https://developer.blender.org/D11234
2021-05-26 16:46:20 +02:00
a6f3bb36df Merge branch 'blender-v2.93-release' 2021-05-26 16:34:03 +02:00
d5a5575685 Fix: GPencil mask shows in view layer render
Currently when rendering the view layer of a grease pencil layer that has
a mask layer attached, the mask layer would show in the rendered image.
This is inconsistent with the default behaviour with no mask on the
grease pencil layer, because it would only render what's on that
particular layer and not anything from any other layer.

This patch makes the masks invisible in the render.

Note: This might seem like not the best solution, but because masks are
just regular grease pencil layers, it's tricky to pass this edge-case to the
drawing code. The way it is handled right now is the best I could come
up with, without making changes that could affect something else.

Reviewed By: antoniov

Maniphest Tasks: T88202

Differential Revision: https://developer.blender.org/D11403
2021-05-26 16:32:58 +02:00
3f27efd31e Merge branch 'blender-v2.93-release' 2021-05-26 16:20:24 +02:00
ba5b4d1bd6 Fix T88250: crash when instancing object in disabled collection
This issue was that `BKE_object_eval_uber_data` was not called for
the text object, because its geometry was not dependent upon
and its `is_directly_visible` tag was `false`. The crash happens in
rendering code, because the evaluated data is missing.

This not only affects text objects, but all object types that have a
geometry component that geometry nodes does not support yet.

The solution is to just add the missing dependencies.

Differential Revision: https://developer.blender.org/D11385
2021-05-26 16:06:01 +02:00
06f86dd4d9 GPencil: Bake GPencil object transforms into a new GPencil object
This operator is a common request of animators to convert the transformation (inluding modifiers) of one grease pencil object, into a new object, generating strokes.

Reviewed By: pepeland

Maniphest Tasks: T87424

Differential Revision: https://developer.blender.org/D11014
2021-05-26 15:43:40 +02:00
b67423f806 Nodes: fix threading issues with node ui storage
Calling BKE_nodetree_attribute_hint_add from multiple threads still
was not safe before..
One issue was that context_map embedded its values directly. So
when context_map grows, all NodeUIStorage would move as well.
I could patch around that by using std::unique_ptr in a few places,
but that just becomes too complex for now.
Instead I simplified the locking a bit by adding just locking a mutex
in NodeTreeUIStorage all the time while an attribute hint is added.

Differential Revision: https://developer.blender.org/D11399
2021-05-26 14:19:01 +02:00
afec66c024 Fix T88588: crash when muting node with multi input socket
The bug existed before the new evaluator already, but the new evaluator
is more sensitive to this kind of error.
2021-05-26 12:25:48 +02:00
12a06292af Cycles: optimize attributes device updates
When an `AttributeSet` is tagged as modified, which happens after the addition or
removal of an `Attribute` from the set, during the following GeometryManager device
update, we update and repack the kernel data for all attribute types. However, if we
only add or remove a `float` attribute, `float2` or `float3` attributes should not
be repacked for efficiency.

This patch adds some mechanisms to detect which attribute types are modified from
the AttributeSet.

Firstly, this adds an `AttrKernelDataType` to map the data type of the Attribute to
the one used in the kernel as there is no one to one match between the two since e.g.
`Transform` or `float4` data are stored as `float3s` in the kernel.

Then, this replaces the `AttributeSet.modified` boolean with a set of flags to detect
which types have been modified. There is no specific flag type (e.g.
`enum ModifiedType`), rather the flags used derive simply from the
`AttrKernelDataType` enumeration, to keep things synchronized.

The logic to remove an `Attribute` from the `AttributeSet` and tag the latter as modified
is centralized in a new `AttributeSet.remove` method taking an iterator as input.

Lastly, as some attributes like standard normals are not stored in the various
kernel attribute arrays (`DeviceScene::attribute_*`), the modified flags are only
set if the associated standard corresponds to an attribute which will be stored
in the kernel's attribute arrays. This makes it so adding or removing such attributes
does not trigger an unnecessary update of other type-related attributes.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D11373
2021-05-26 12:18:28 +02:00
2a09634d41 Fix particlesystem not duplicating their pointcache in NO_MAIN case.
Sharing data between duplicated IDs should be restricted to depsgraph
(CoW) cases, not all NO_MAIN ones...

While this was probably not an issue currently, we aim at using more and
more out-of-main IDs for temp data processing.

NOTE: Somewhat related to T88555, and similar issue as the one fixed in
rBdfb963c70df5.
2021-05-26 11:25:51 +02:00
534fcab994 Fix T88552: Cycles changing Render Passes in viewport does not work 2021-05-26 11:16:47 +02:00
a72a580948 Cleanup: Simplify Cycles viewport parameters
Use early output and access shading RNA object only once.
2021-05-26 11:10:56 +02:00
e6c0e6c2a9 Compositor: Use BLI_color in convert alpha node.
Recently the CPP colors module landed in master. This patch will use the
new module in the convert alpha node.
2021-05-26 10:46:46 +02:00
1e6b028580 Cleanup: Remove unused argument in Cycles sync
Makes it easier to see where exactly the viewport is used.
2021-05-26 10:45:27 +02:00
09e77f904d Fix T88534: Unable to add a Geometry Node Tree on Volume object
Volumes are supported, poll corrected.

Maniphest Tasks: T88534

Differential Revision: https://developer.blender.org/D11378
2021-05-26 10:28:16 +02:00
03c0fa1cdb Fix T88531: Mantaflow problem with geometry nodes
Objects modified by geometry nodes modifiers were not caught as being
"dynamic".

Now add this modifier type to the list of modifiers making them "dynamic"
in the eyes of mantaflow.

(noticed by @sebbas in chat)

Maniphest Tasks: T88531

Differential Revision: https://developer.blender.org/D11389
2021-05-26 10:21:39 +02:00
b813007a0f Fix T88566: Mantaflow inflow with shapekeys is not working anymore
(regression)

Code was actually checking for shapekeys, but these were not detected
properly (some effects like shape keys are added as virtual modifiers
before the user created modifiers)

Now go over virtual modifiers as well.

Maniphest Tasks: T88566

Differential Revision: https://developer.blender.org/D11388
2021-05-26 10:17:55 +02:00
ff35332610 Merge branch 'blender-v2.93-release' 2021-05-26 09:55:47 +02:00
ebde6e1852 Fix T88251: "Operator Cheat Sheet" Crash
Caused by {rB919558854d62}.

Same fix as in {rBdc8a43c8755a} -- let RNA enum item callbacks check
for NULL context.

The NULL context is used to extract items for document generation.

Maniphest Tasks: T88251

Differential Revision: https://developer.blender.org/D11391
2021-05-26 09:51:55 +02:00
c0bb7d9cb7 Cleanup: Fix short comparison with bool warning
For some reason the hide status is stored in a short and a char
(we cannot have bools in DNA).
2021-05-25 14:37:58 -04:00
95690dd362 Bump FFmpeg version from 4.2.3 to 4.4
Bump FFmpeg version to 4.4 to fix a problem where it would write the
wrong frame rate. Their old API was deprecated and Blender moved to the
new one in rB8d6264ea12bfac0912c7249f00af2ac8e3409ed1. The new one
produced files with the wrong frame rate, which was fixed in FFmpeg 4.4.

Manifest Task: T88568

Reviewed By: LazyDodo, zeddb

Differential Revision: https://developer.blender.org/D11392
2021-05-25 18:58:28 +02:00
490dd279cc deps: Fix broken boost link 2021-05-25 10:27:48 -06:00
45b28c0f88 GPencil: Add a use_light option when creating object.
This option is default off when creating line art objects
because line art seldom use lighting and the normal data
would be all over the place anyway.

Reviewed By: Antonio Vazquez (antoniov)

Differential Revision: https://developer.blender.org/D11372
2021-05-25 23:45:47 +08:00
9f60188cd8 Cleanup: Use ListBase in various places in line art.
This clarifies the data structures for storing edges
for different calculation stages.

Reviewed By: Sebastian Parborg (zeddb)

Differential Revision: https://developer.blender.org/D11386
2021-05-25 23:32:04 +08:00
Jeroen Bakker
cb8a6814fd Blenlib: Explicit Colors.
Colors are often thought of as being 4 values that make up that can make any color.
But that is of course too limited. In C we didn’t spend time to annotate what we meant
when using colors.

Recently `BLI_color.hh` was made to facilitate color structures in CPP. CPP has possibilities to
enforce annotating structures during compilation and can adds conversions between them using
function overloading and explicit constructors.

The storage structs can hold 4 channels (r, g, b and a).

Usage:

Convert a theme byte color to a linearrgb premultiplied.
```
ColorTheme4b theme_color;
ColorSceneLinear4f<eAlpha::Premultiplied> linearrgb_color =
    BLI_color_convert_to_scene_linear(theme_color).premultiply_alpha();
```

The API is structured to make most use of inlining. Most notable are space
conversions done via `BLI_color_convert_to*` functions.

- Conversions between spaces (theme <=> scene linear) should always be done by
  invoking the `BLI_color_convert_to*` methods.
- Encoding colors (compressing to store colors inside a less precision storage)
  should be done by invoking the `encode` and `decode` methods.
- Changing alpha association should be done by invoking `premultiply_alpha` or
  `unpremultiply_alpha` methods.

# Encoding.

Color encoding is used to store colors with less precision as in using `uint8_t` in
stead of `float`. This encoding is supported for `eSpace::SceneLinear`.
To make this clear to the developer the `eSpace::SceneLinearByteEncoded`
space is added.

# Precision

Colors can be stored using `uint8_t` or `float` colors. The conversion
between the two precisions are available as methods. (`to_4b` and
`to_4f`).

# Alpha conversion

Alpha conversion is only supported in SceneLinear space.

Extending:
- This file can be extended with `ColorHex/Hsl/Hsv` for different representations
  of rgb based colors. `ColorHsl4f<eSpace::SceneLinear, eAlpha::Premultiplied>`
- Add non RGB spaces/storages ColorXyz.

Reviewed By: JacquesLucke, brecht

Differential Revision: https://developer.blender.org/D10978
2021-05-25 17:16:54 +02:00
00955cd31e Revert "Blenlib: Explicit Colors."
This reverts commit fd94e03344.
does not compile against latest master.
2021-05-25 17:03:54 +02:00
Jeroen Bakker
fd94e03344 Blenlib: Explicit Colors.
Colors are often thought of as being 4 values that make up that can make any color.
But that is of course too limited. In C we didn’t spend time to annotate what we meant
when using colors.

Recently `BLI_color.hh` was made to facilitate color structures in CPP. CPP has possibilities to
enforce annotating structures during compilation and can adds conversions between them using
function overloading and explicit constructors.

The storage structs can hold 4 channels (r, g, b and a).

Usage:

Convert a theme byte color to a linearrgb premultiplied.
```
ColorTheme4b theme_color;
ColorSceneLinear4f<eAlpha::Premultiplied> linearrgb_color =
    BLI_color_convert_to_scene_linear(theme_color).premultiply_alpha();
```

The API is structured to make most use of inlining. Most notable are space
conversions done via `BLI_color_convert_to*` functions.

- Conversions between spaces (theme <=> scene linear) should always be done by
  invoking the `BLI_color_convert_to*` methods.
- Encoding colors (compressing to store colors inside a less precision storage)
  should be done by invoking the `encode` and `decode` methods.
- Changing alpha association should be done by invoking `premultiply_alpha` or
  `unpremultiply_alpha` methods.

# Encoding.

Color encoding is used to store colors with less precision as in using `uint8_t` in
stead of `float`. This encoding is supported for `eSpace::SceneLinear`.
To make this clear to the developer the `eSpace::SceneLinearByteEncoded`
space is added.

# Precision

Colors can be stored using `uint8_t` or `float` colors. The conversion
between the two precisions are available as methods. (`to_4b` and
`to_4f`).

# Alpha conversion

Alpha conversion is only supported in SceneLinear space.

Extending:
- This file can be extended with `ColorHex/Hsl/Hsv` for different representations
  of rgb based colors. `ColorHsl4f<eSpace::SceneLinear, eAlpha::Premultiplied>`
- Add non RGB spaces/storages ColorXyz.

Reviewed By: JacquesLucke, brecht

Differential Revision: https://developer.blender.org/D10978
2021-05-25 17:01:26 +02:00
b046bc536b Fix T88096: Baking with OptiX and displacement fails
Using displacement runs the shader eval kernel, but since OptiX modules are not loaded when
baking is active, those were not available and therefore failed to launch. This fixes that by falling
back to the CUDA kernels.
2021-05-25 16:56:16 +02:00
8cd506639a Geometry Nodes: Add Shader Curve Nodes
Convert curve vec and curve rgb shader nodes to geometry nodes, based on node_shader_valToRgb.cc implementation.
2021-05-25 15:43:51 +01:00
54ae7baa4c Cleanup: Line art naming changes.
Make variable naming consistent with struct names.

Reviewed By: Sebastian Parborg (zeddb)

Differential Revision: https://developer.blender.org/D11382
2021-05-25 22:11:06 +08:00
c3c576c8c4 Cleanup: Convert to static type directly from CPPType 2021-05-25 09:52:08 -04:00
a20ef4207d Fix T86956: VSE shading mode ignores Grease Pencil Vertex colors.
Issue is that due to the strange definition of render in grease pencil
(meaning should be rendered similar to rendering). This included normal
viewport rendering in OB_RENDER and OpenGL render in OB_RENDER.

For other rendering modes the overlay vertex opacity would be used. This
patch sets this value to 1 when rendering via a scene strip override.

NOTE: that this isn't a good solution as I expect that users want to use
the opacity of the Grease pencil object. Perhaps the GPencil team has a
better solution for it.
2021-05-25 15:18:06 +02:00
1bdd5becc7 Unreported fix: vertex colors overlay not set for new 3d views.
Found during researching {T86956}.
2021-05-25 15:18:06 +02:00
397 changed files with 13273 additions and 10391 deletions

View File

@@ -43,7 +43,7 @@ set(JPEG_FILE libjpeg-turbo-${JPEG_VERSION}.tar.gz)
set(BOOST_VERSION 1.73.0)
set(BOOST_VERSION_NODOTS 1_73_0)
set(BOOST_VERSION_NODOTS_SHORT 1_73)
set(BOOST_URI https://dl.bintray.com/boostorg/release/${BOOST_VERSION}/source/boost_${BOOST_VERSION_NODOTS}.tar.gz)
set(BOOST_URI https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION}/source/boost_${BOOST_VERSION_NODOTS}.tar.gz)
set(BOOST_HASH 4036cd27ef7548b8d29c30ea10956196)
set(BOOST_HASH_TYPE MD5)
set(BOOST_FILE boost_${BOOST_VERSION_NODOTS}.tar.gz)
@@ -297,10 +297,10 @@ set(OPENJPEG_HASH 63f5a4713ecafc86de51bfad89cc07bb788e9bba24ebbf0c4ca637621aadb6
set(OPENJPEG_HASH_TYPE SHA256)
set(OPENJPEG_FILE openjpeg-v${OPENJPEG_VERSION}.tar.gz)
set(FFMPEG_VERSION 4.2.3)
set(FFMPEG_VERSION 4.4)
set(FFMPEG_URI http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2)
set(FFMPEG_HASH 695fad11f3baf27784e24cb0e977b65a)
set(FFMPEG_HASH_TYPE MD5)
set(FFMPEG_HASH 42093549751b582cf0f338a21a3664f52e0a9fbe0d238d3c992005e493607d0e)
set(FFMPEG_HASH_TYPE SHA256)
set(FFMPEG_FILE ffmpeg-${FFMPEG_VERSION}.tar.bz2)
set(FFTW_VERSION 3.3.8)

View File

@@ -563,9 +563,9 @@ OIDN_SKIP=false
ISPC_VERSION="1.14.1"
FFMPEG_VERSION="4.2.3"
FFMPEG_VERSION_SHORT="4.2"
FFMPEG_VERSION_MIN="3.0"
FFMPEG_VERSION="4.4"
FFMPEG_VERSION_SHORT="4.4"
FFMPEG_VERSION_MIN="4.4"
FFMPEG_VERSION_MAX="5.0"
FFMPEG_FORCE_BUILD=false
FFMPEG_FORCE_REBUILD=false

View File

@@ -1,70 +1,4 @@
Blender Buildbot
================
Buildbot Configuration
=====================
Code signing
------------
Code signing is done as part of INSTALL target, which makes it possible to sign
files which are aimed into a bundle and coming from a non-signed source (such as
libraries SVN).
This is achieved by specifying `worker_codesign.cmake` as a post-install script
run by CMake. This CMake script simply involves an utility script written in
Python which takes care of an actual signing.
### Configuration
Client configuration doesn't need anything special, other than variable
`SHARED_STORAGE_DIR` pointing to a location which is watched by a server.
This is done in `config_builder.py` file and is stored in Git (which makes it
possible to have almost zero-configuration buildbot machines).
Server configuration requires copying `config_server_template.py` under the
name of `config_server.py` and tweaking values, which are platform-specific.
#### Windows configuration
There are two things which are needed on Windows in order to have code signing
to work:
- `TIMESTAMP_AUTHORITY_URL` which is most likely set http://timestamp.digicert.com
- `CERTIFICATE_FILEPATH` which is a full file path to a PKCS #12 key (.pfx).
## Tips
### Self-signed certificate on Windows
It is easiest to test configuration using self-signed certificate.
The certificate manipulation utilities are coming with Windows SDK.
Unfortunately, they are not added to PATH. Here is an example of how to make
sure they are easily available:
```
set PATH=C:\Program Files (x86)\Windows Kits\10\App Certification Kit;%PATH%
set PATH=C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64;%PATH%
```
Generate CA:
```
makecert -r -pe -n "CN=Blender Test CA" -ss CA -sr CurrentUser -a sha256 ^
-cy authority -sky signature -sv BlenderTestCA.pvk BlenderTestCA.cer
```
Import the generated CA:
```
certutil -user -addstore Root BlenderTestCA.cer
```
Create self-signed certificate and pack it into PKCS #12:
```
makecert -pe -n "CN=Blender Test SPC" -a sha256 -cy end ^
-sky signature ^
-ic BlenderTestCA.cer -iv BlenderTestCA.pvk ^
-sv BlenderTestSPC.pvk BlenderTestSPC.cer
pvk2pfx -pvk BlenderTestSPC.pvk -spc BlenderTestSPC.cer -pfx BlenderTestSPC.pfx
```
Files used by Buildbot's `compile-code` step.

View File

@@ -1,127 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import argparse
import os
import re
import subprocess
import sys
def is_tool(name):
"""Check whether `name` is on PATH and marked as executable."""
# from whichcraft import which
from shutil import which
return which(name) is not None
class Builder:
def __init__(self, name, branch, codesign):
self.name = name
self.branch = branch
self.is_release_branch = re.match("^blender-v(.*)-release$", branch) is not None
self.codesign = codesign
# Buildbot runs from build/ directory
self.blender_dir = os.path.abspath(os.path.join('..', 'blender.git'))
self.build_dir = os.path.abspath(os.path.join('..', 'build'))
self.install_dir = os.path.abspath(os.path.join('..', 'install'))
self.upload_dir = os.path.abspath(os.path.join('..', 'install'))
# Detect platform
if name.startswith('mac'):
self.platform = 'mac'
self.command_prefix = []
elif name.startswith('linux'):
self.platform = 'linux'
if is_tool('scl'):
self.command_prefix = ['scl', 'enable', 'devtoolset-9', '--']
else:
self.command_prefix = []
elif name.startswith('win'):
self.platform = 'win'
self.command_prefix = []
else:
raise ValueError('Unkonw platform for builder ' + self.platform)
# Always 64 bit now
self.bits = 64
def create_builder_from_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('builder_name')
parser.add_argument('branch', default='master', nargs='?')
parser.add_argument("--codesign", action="store_true")
args = parser.parse_args()
return Builder(args.builder_name, args.branch, args.codesign)
class VersionInfo:
def __init__(self, builder):
# Get version information
buildinfo_h = os.path.join(builder.build_dir, "source", "creator", "buildinfo.h")
blender_h = os.path.join(builder.blender_dir, "source", "blender", "blenkernel", "BKE_blender_version.h")
version_number = int(self._parse_header_file(blender_h, 'BLENDER_VERSION'))
version_number_patch = int(self._parse_header_file(blender_h, 'BLENDER_VERSION_PATCH'))
version_numbers = (version_number // 100, version_number % 100, version_number_patch)
self.short_version = "%d.%d" % (version_numbers[0], version_numbers[1])
self.version = "%d.%d.%d" % version_numbers
self.version_cycle = self._parse_header_file(blender_h, 'BLENDER_VERSION_CYCLE')
self.hash = self._parse_header_file(buildinfo_h, 'BUILD_HASH')[1:-1]
if self.version_cycle == "release":
# Final release
self.full_version = self.version
self.is_development_build = False
elif self.version_cycle == "rc":
# Release candidate
self.full_version = self.version + self.version_cycle
self.is_development_build = False
else:
# Development build
self.full_version = self.version + '-' + self.hash
self.is_development_build = True
def _parse_header_file(self, filename, define):
import re
regex = re.compile(r"^#\s*define\s+%s\s+(.*)" % define)
with open(filename, "r") as file:
for l in file:
match = regex.match(l)
if match:
return match.group(1)
return None
def call(cmd, env=None, exit_on_error=True):
print(' '.join(cmd))
# Flush to ensure correct order output on Windows.
sys.stdout.flush()
sys.stderr.flush()
retcode = subprocess.call(cmd, env=env)
if exit_on_error and retcode != 0:
sys.exit(retcode)
return retcode

View File

@@ -1,81 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
from dataclasses import dataclass
from pathlib import Path
from typing import List
@dataclass
class AbsoluteAndRelativeFileName:
"""
Helper class which keeps track of absolute file path for a direct access and
corresponding relative path against given base.
The relative part is used to construct a file name within an archive which
contains files which are to be signed or which has been signed already
(depending on whether the archive is addressed to signing server or back
to the buildbot worker).
"""
# Base directory which is where relative_filepath is relative to.
base_dir: Path
# Full absolute path of the corresponding file.
absolute_filepath: Path
# Derived from full file path, contains part of the path which is relative
# to a desired base path.
relative_filepath: Path
def __init__(self, base_dir: Path, filepath: Path):
self.base_dir = base_dir
self.absolute_filepath = filepath.resolve()
self.relative_filepath = self.absolute_filepath.relative_to(
self.base_dir)
@classmethod
def from_path(cls, path: Path) -> 'AbsoluteAndRelativeFileName':
assert path.is_absolute()
assert path.is_file()
base_dir = path.parent
return AbsoluteAndRelativeFileName(base_dir, path)
@classmethod
def recursively_from_directory(cls, base_dir: Path) \
-> List['AbsoluteAndRelativeFileName']:
"""
Create list of AbsoluteAndRelativeFileName for all the files in the
given directory.
NOTE: Result will be pointing to a resolved paths.
"""
assert base_dir.is_absolute()
assert base_dir.is_dir()
base_dir = base_dir.resolve()
result = []
for filename in base_dir.glob('**/*'):
if not filename.is_file():
continue
result.append(AbsoluteAndRelativeFileName(base_dir, filename))
return result

View File

@@ -1,245 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import dataclasses
import json
import os
from pathlib import Path
from typing import Optional
import codesign.util as util
class ArchiveStateError(Exception):
message: str
def __init__(self, message):
self.message = message
super().__init__(self.message)
@dataclasses.dataclass
class ArchiveState:
"""
Additional information (state) of the archive
Includes information like expected file size of the archive file in the case
the archive file is expected to be successfully created.
If the archive can not be created, this state will contain error message
indicating details of error.
"""
# Size in bytes of the corresponding archive.
file_size: Optional[int] = None
# Non-empty value indicates that error has happenned.
error_message: str = ''
def has_error(self) -> bool:
"""
Check whether the archive is at error state
"""
return self.error_message
def serialize_to_string(self) -> str:
payload = dataclasses.asdict(self)
return json.dumps(payload, sort_keys=True, indent=4)
def serialize_to_file(self, filepath: Path) -> None:
string = self.serialize_to_string()
filepath.write_text(string)
@classmethod
def deserialize_from_string(cls, string: str) -> 'ArchiveState':
try:
object_as_dict = json.loads(string)
except json.decoder.JSONDecodeError:
raise ArchiveStateError('Error parsing JSON')
return cls(**object_as_dict)
@classmethod
def deserialize_from_file(cls, filepath: Path):
string = filepath.read_text()
return cls.deserialize_from_string(string)
class ArchiveWithIndicator:
"""
The idea of this class is to wrap around logic which takes care of keeping
track of a name of an archive and synchronization routines between buildbot
worker and signing server.
The synchronization is done based on creating a special file after the
archive file is knowingly ready for access.
"""
# Base directory where the archive is stored (basically, a basename() of
# the absolute archive file name).
#
# For example, 'X:\\TEMP\\'.
base_dir: Path
# Absolute file name of the archive.
#
# For example, 'X:\\TEMP\\FOO.ZIP'.
archive_filepath: Path
# Absolute name of a file which acts as an indication of the fact that the
# archive is ready and is available for access.
#
# This is how synchronization between buildbot worker and signing server is
# done:
# - First, the archive is created under archive_filepath name.
# - Second, the indication file is created under ready_indicator_filepath
# name.
# - Third, the colleague of whoever created the indicator name watches for
# the indication file to appear, and once it's there it access the
# archive.
ready_indicator_filepath: Path
def __init__(
self, base_dir: Path, archive_name: str, ready_indicator_name: str):
"""
Construct the object from given base directory and name of the archive
file:
ArchiveWithIndicator(Path('X:\\TEMP'), 'FOO.ZIP', 'INPUT_READY')
"""
self.base_dir = base_dir
self.archive_filepath = self.base_dir / archive_name
self.ready_indicator_filepath = self.base_dir / ready_indicator_name
def is_ready_unsafe(self) -> bool:
"""
Check whether the archive is ready for access.
No guarding about possible network failres is done here.
"""
if not self.ready_indicator_filepath.exists():
return False
try:
archive_state = ArchiveState.deserialize_from_file(
self.ready_indicator_filepath)
except ArchiveStateError as error:
print(f'Error deserializing archive state: {error.message}')
return False
if archive_state.has_error():
# If the error did happen during codesign procedure there will be no
# corresponding archive file.
# The caller code will deal with the error check further.
return True
# Sometimes on macOS indicator file appears prior to the actual archive
# despite the order of creation and os.sync() used in tag_ready().
# So consider archive not ready if there is an indicator without an
# actual archive.
if not self.archive_filepath.exists():
print('Found indicator without actual archive, waiting for archive '
f'({self.archive_filepath}) to appear.')
return False
# Wait for until archive is fully stored.
actual_archive_size = self.archive_filepath.stat().st_size
if actual_archive_size != archive_state.file_size:
print('Partial/invalid archive size (expected '
f'{archive_state.file_size} got {actual_archive_size})')
return False
return True
def is_ready(self) -> bool:
"""
Check whether the archive is ready for access.
Will tolerate possible network failures: if there is a network failure
or if there is still no proper permission on a file False is returned.
"""
# There are some intermitten problem happening at a random which is
# translates to "OSError : [WinError 59] An unexpected network error occurred".
# Some reports suggests it might be due to lack of permissions to the file,
# which might be applicable in our case since it's possible that file is
# initially created with non-accessible permissions and gets chmod-ed
# after initial creation.
try:
return self.is_ready_unsafe()
except OSError as e:
print(f'Exception checking archive: {e}')
return False
def tag_ready(self, error_message='') -> None:
"""
Tag the archive as ready by creating the corresponding indication file.
NOTE: It is expected that the archive was never tagged as ready before
and that there are no subsequent tags of the same archive.
If it is violated, an assert will fail.
"""
assert not self.is_ready()
# Try the best to make sure everything is synced to the file system,
# to avoid any possibility of stamp appearing on a network share prior to
# an actual file.
if util.get_current_platform() != util.Platform.WINDOWS:
os.sync()
archive_size = -1
if self.archive_filepath.exists():
archive_size = self.archive_filepath.stat().st_size
archive_info = ArchiveState(
file_size=archive_size, error_message=error_message)
self.ready_indicator_filepath.write_text(
archive_info.serialize_to_string())
def get_state(self) -> ArchiveState:
"""
Get state object for this archive
The state is read from the corresponding state file.
"""
try:
return ArchiveState.deserialize_from_file(self.ready_indicator_filepath)
except ArchiveStateError as error:
return ArchiveState(error_message=f'Error in information format: {error}')
def clean(self) -> None:
"""
Remove both archive and the ready indication file.
"""
util.ensure_file_does_not_exist_or_die(self.ready_indicator_filepath)
util.ensure_file_does_not_exist_or_die(self.archive_filepath)
def is_fully_absent(self) -> bool:
"""
Check whether both archive and its ready indicator are absent.
Is used for a sanity check during code signing process by both
buildbot worker and signing server.
"""
return (not self.archive_filepath.exists() and
not self.ready_indicator_filepath.exists())

View File

@@ -1,501 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Signing process overview.
#
# From buildbot worker side:
# - Files which needs to be signed are collected from either a directory to
# sign all signable files in there, or by filename of a single file to sign.
# - Those files gets packed into an archive and stored in a location location
# which is watched by the signing server.
# - A marker READY file is created which indicates the archive is ready for
# access.
# - Wait for the server to provide an archive with signed files.
# This is done by watching for the READY file which corresponds to an archive
# coming from the signing server.
# - Unpack the signed signed files from the archives and replace original ones.
#
# From code sign server:
# - Watch special location for a READY file which indicates the there is an
# archive with files which are to be signed.
# - Unpack the archive to a temporary location.
# - Run codesign tool and make sure all the files are signed.
# - Pack the signed files and store them in a location which is watched by
# the buildbot worker.
# - Create a READY file which indicates that the archive with signed files is
# ready.
import abc
import logging
import shutil
import subprocess
import time
import tarfile
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Iterable, List
import codesign.util as util
from codesign.absolute_and_relative_filename import AbsoluteAndRelativeFileName
from codesign.archive_with_indicator import ArchiveWithIndicator
from codesign.exception import CodeSignException
logger = logging.getLogger(__name__)
logger_builder = logger.getChild('builder')
logger_server = logger.getChild('server')
def pack_files(files: Iterable[AbsoluteAndRelativeFileName],
archive_filepath: Path) -> None:
"""
Create tar archive from given files for the signing pipeline.
Is used by buildbot worker to create an archive of files which are to be
signed, and by signing server to send signed files back to the worker.
"""
with tarfile.TarFile.open(archive_filepath, 'w') as tar_file_handle:
for file_info in files:
tar_file_handle.add(file_info.absolute_filepath,
arcname=file_info.relative_filepath)
def extract_files(archive_filepath: Path,
extraction_dir: Path) -> None:
"""
Extract all files form the given archive into the given direcotry.
"""
# TODO(sergey): Verify files in the archive have relative path.
with tarfile.TarFile.open(archive_filepath, mode='r') as tar_file_handle:
tar_file_handle.extractall(path=extraction_dir)
class BaseCodeSigner(metaclass=abc.ABCMeta):
"""
Base class for a platform-specific signer of binaries.
Contains all the logic shared across platform-specific implementations, such
as synchronization and notification logic.
Platform specific bits (such as actual command for signing the binary) are
to be implemented as a subclass.
Provides utilities code signing as a whole, including functionality needed
by a signing server and a buildbot worker.
The signer and builder may run on separate machines, the only requirement is
that they have access to a directory which is shared between them. For the
security concerns this is to be done as a separate machine (or as a Shared
Folder configuration in VirtualBox configuration). This directory might be
mounted under different base paths, but its underlying storage is to be
the same.
The code signer is short-lived on a buildbot worker side, and is living
forever on a code signing server side.
"""
# TODO(sergey): Find a neat way to have config annotated.
# config: Config
# Storage directory where builder puts files which are requested to be
# signed.
# Consider this an input of the code signing server.
unsigned_storage_dir: Path
# Storage where signed files are stored.
# Consider this an output of the code signer server.
signed_storage_dir: Path
# Platform the code is currently executing on.
platform: util.Platform
def __init__(self, config):
self.config = config
absolute_shared_storage_dir = config.SHARED_STORAGE_DIR.resolve()
# Unsigned (signing server input) configuration.
self.unsigned_storage_dir = absolute_shared_storage_dir / 'unsigned'
# Signed (signing server output) configuration.
self.signed_storage_dir = absolute_shared_storage_dir / 'signed'
self.platform = util.get_current_platform()
def cleanup_environment_for_builder(self) -> None:
# TODO(sergey): Revisit need of cleaning up the existing files.
# In practice it wasn't so helpful, and with multiple clients
# talking to the same server it becomes even more tricky.
pass
def cleanup_environment_for_signing_server(self) -> None:
# TODO(sergey): Revisit need of cleaning up the existing files.
# In practice it wasn't so helpful, and with multiple clients
# talking to the same server it becomes even more tricky.
pass
def generate_request_id(self) -> str:
"""
Generate an unique identifier for code signing request.
"""
return str(uuid.uuid4())
def archive_info_for_request_id(
self, path: Path, request_id: str) -> ArchiveWithIndicator:
return ArchiveWithIndicator(
path, f'{request_id}.tar', f'{request_id}.ready')
def signed_archive_info_for_request_id(
self, request_id: str) -> ArchiveWithIndicator:
return self.archive_info_for_request_id(
self.signed_storage_dir, request_id)
def unsigned_archive_info_for_request_id(
self, request_id: str) -> ArchiveWithIndicator:
return self.archive_info_for_request_id(
self.unsigned_storage_dir, request_id)
############################################################################
# Buildbot worker side helpers.
@abc.abstractmethod
def check_file_is_to_be_signed(
self, file: AbsoluteAndRelativeFileName) -> bool:
"""
Check whether file is to be signed.
Is used by both single file signing pipeline and recursive directory
signing pipeline.
This is where code signer is to check whether file is to be signed or
not. This check might be based on a simple extension test or on actual
test whether file have a digital signature already or not.
"""
def collect_files_to_sign(self, path: Path) \
-> List[AbsoluteAndRelativeFileName]:
"""
Get all files which need to be signed from the given path.
NOTE: The path might either be a file or directory.
This function is run from the buildbot worker side.
"""
# If there is a single file provided trust the buildbot worker that it
# is eligible for signing.
if path.is_file():
file = AbsoluteAndRelativeFileName.from_path(path)
if not self.check_file_is_to_be_signed(file):
return []
return [file]
all_files = AbsoluteAndRelativeFileName.recursively_from_directory(
path)
files_to_be_signed = [file for file in all_files
if self.check_file_is_to_be_signed(file)]
return files_to_be_signed
def wait_for_signed_archive_or_die(self, request_id) -> None:
"""
Wait until archive with signed files is available.
Will only return if the archive with signed files is available. If there
was an error during code sign procedure the SystemExit exception is
raised, with the message set to the error reported by the codesign
server.
Will only wait for the configured time. If that time exceeds and there
is still no responce from the signing server the application will exit
with a non-zero exit code.
"""
signed_archive_info = self.signed_archive_info_for_request_id(
request_id)
unsigned_archive_info = self.unsigned_archive_info_for_request_id(
request_id)
timeout_in_seconds = self.config.TIMEOUT_IN_SECONDS
time_start = time.monotonic()
while not signed_archive_info.is_ready():
time.sleep(1)
time_slept_in_seconds = time.monotonic() - time_start
if time_slept_in_seconds > timeout_in_seconds:
signed_archive_info.clean()
unsigned_archive_info.clean()
raise SystemExit("Signing server didn't finish signing in "
f'{timeout_in_seconds} seconds, dying :(')
archive_state = signed_archive_info.get_state()
if archive_state.has_error():
signed_archive_info.clean()
unsigned_archive_info.clean()
raise SystemExit(
f'Error happenned during codesign procedure: {archive_state.error_message}')
def copy_signed_files_to_directory(
self, signed_dir: Path, destination_dir: Path) -> None:
"""
Copy all files from signed_dir to destination_dir.
This function will overwrite any existing file. Permissions are copied
from the source files, but other metadata, such as timestamps, are not.
"""
for signed_filepath in signed_dir.glob('**/*'):
if not signed_filepath.is_file():
continue
relative_filepath = signed_filepath.relative_to(signed_dir)
destination_filepath = destination_dir / relative_filepath
destination_filepath.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(signed_filepath, destination_filepath)
def run_buildbot_path_sign_pipeline(self, path: Path) -> None:
"""
Run all steps needed to make given path signed.
Path points to an unsigned file or a directory which contains unsigned
files.
If the path points to a single file then this file will be signed.
This is used to sign a final bundle such as .msi on Windows or .dmg on
macOS.
NOTE: The code signed implementation might actually reject signing the
file, in which case the file will be left unsigned. This isn't anything
to be considered a failure situation, just might happen when buildbot
worker can not detect whether signing is really required in a specific
case or not.
If the path points to a directory then code signer will sign all
signable files from it (finding them recursively).
"""
self.cleanup_environment_for_builder()
# Make sure storage directory exists.
self.unsigned_storage_dir.mkdir(parents=True, exist_ok=True)
# Collect all files which needs to be signed and pack them into a single
# archive which will be sent to the signing server.
logger_builder.info('Collecting files which are to be signed...')
files = self.collect_files_to_sign(path)
if not files:
logger_builder.info('No files to be signed, ignoring.')
return
logger_builder.info('Found %d files to sign.', len(files))
request_id = self.generate_request_id()
signed_archive_info = self.signed_archive_info_for_request_id(
request_id)
unsigned_archive_info = self.unsigned_archive_info_for_request_id(
request_id)
pack_files(files=files,
archive_filepath=unsigned_archive_info.archive_filepath)
unsigned_archive_info.tag_ready()
# Wait for the signing server to finish signing.
logger_builder.info('Waiting signing server to sign the files...')
self.wait_for_signed_archive_or_die(request_id)
# Extract signed files from archive and move files to final location.
with TemporaryDirectory(prefix='blender-buildbot-') as temp_dir_str:
unpacked_signed_files_dir = Path(temp_dir_str)
logger_builder.info('Extracting signed files from archive...')
extract_files(
archive_filepath=signed_archive_info.archive_filepath,
extraction_dir=unpacked_signed_files_dir)
destination_dir = path
if destination_dir.is_file():
destination_dir = destination_dir.parent
self.copy_signed_files_to_directory(
unpacked_signed_files_dir, destination_dir)
logger_builder.info('Removing archive with signed files...')
signed_archive_info.clean()
############################################################################
# Signing server side helpers.
def wait_for_sign_request(self) -> str:
"""
Wait for the buildbot to request signing of an archive.
Returns an identifier of signing request.
"""
# TOOD(sergey): Support graceful shutdown on Ctrl-C.
logger_server.info(
f'Waiting for a request directory {self.unsigned_storage_dir} to appear.')
while not self.unsigned_storage_dir.exists():
time.sleep(1)
logger_server.info(
'Waiting for a READY indicator of any signing request.')
request_id = None
while request_id is None:
for file in self.unsigned_storage_dir.iterdir():
if file.suffix != '.ready':
continue
request_id = file.stem
logger_server.info(f'Found READY for request ID {request_id}.')
if request_id is None:
time.sleep(1)
unsigned_archive_info = self.unsigned_archive_info_for_request_id(
request_id)
while not unsigned_archive_info.is_ready():
time.sleep(1)
return request_id
@abc.abstractmethod
def sign_all_files(self, files: List[AbsoluteAndRelativeFileName]) -> None:
"""
Sign all files in the given directory.
NOTE: Signing should happen in-place.
"""
def run_signing_pipeline(self, request_id: str):
"""
Run the full signing pipeline starting from the point when buildbot
worker have requested signing.
"""
# Make sure storage directory exists.
self.signed_storage_dir.mkdir(parents=True, exist_ok=True)
with TemporaryDirectory(prefix='blender-codesign-') as temp_dir_str:
temp_dir = Path(temp_dir_str)
signed_archive_info = self.signed_archive_info_for_request_id(
request_id)
unsigned_archive_info = self.unsigned_archive_info_for_request_id(
request_id)
logger_server.info('Extracting unsigned files from archive...')
extract_files(
archive_filepath=unsigned_archive_info.archive_filepath,
extraction_dir=temp_dir)
logger_server.info('Collecting all files which needs signing...')
files = AbsoluteAndRelativeFileName.recursively_from_directory(
temp_dir)
logger_server.info('Signing all requested files...')
try:
self.sign_all_files(files)
except CodeSignException as error:
signed_archive_info.tag_ready(error_message=error.message)
unsigned_archive_info.clean()
logger_server.info('Signing is complete with errors.')
return
logger_server.info('Packing signed files...')
pack_files(files=files,
archive_filepath=signed_archive_info.archive_filepath)
signed_archive_info.tag_ready()
logger_server.info('Removing signing request...')
unsigned_archive_info.clean()
logger_server.info('Signing is complete.')
def run_signing_server(self):
logger_server.info('Starting new code signing server...')
self.cleanup_environment_for_signing_server()
logger_server.info('Code signing server is ready')
while True:
logger_server.info('Waiting for the signing request in %s...',
self.unsigned_storage_dir)
request_id = self.wait_for_sign_request()
logger_server.info(
f'Beging signign procedure for request ID {request_id}.')
self.run_signing_pipeline(request_id)
############################################################################
# Command executing.
#
# Abstracted to a degree that allows to run commands from a foreign
# platform.
# The goal with this is to allow performing dry-run tests of code signer
# server from other platforms (for example, to test that macOS code signer
# does what it is supposed to after doing a refactor on Linux).
# TODO(sergey): What is the type annotation for the command?
def run_command_or_mock(self, command, platform: util.Platform) -> None:
"""
Run given command if current platform matches given one
If the platform is different then it will only be printed allowing
to verify logic of the code signing process.
"""
if platform != self.platform:
logger_server.info(
f'Will run command for {platform}: {command}')
return
logger_server.info(f'Running command: {command}')
subprocess.run(command)
# TODO(sergey): What is the type annotation for the command?
def check_output_or_mock(self, command,
platform: util.Platform,
allow_nonzero_exit_code=False) -> str:
"""
Run given command if current platform matches given one
If the platform is different then it will only be printed allowing
to verify logic of the code signing process.
If allow_nonzero_exit_code is truth then the output will be returned
even if application quit with non-zero exit code.
Otherwise an subprocess.CalledProcessError exception will be raised
in such case.
"""
if platform != self.platform:
logger_server.info(
f'Will run command for {platform}: {command}')
return
if allow_nonzero_exit_code:
process = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
output = process.communicate()[0]
return output.decode()
logger_server.info(f'Running command: {command}')
return subprocess.check_output(
command, stderr=subprocess.STDOUT).decode()

View File

@@ -1,62 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Configuration of a code signer which is specific to the code running from
# buildbot's worker.
import sys
from pathlib import Path
import codesign.util as util
from codesign.config_common import *
platform = util.get_current_platform()
if platform == util.Platform.LINUX:
SHARED_STORAGE_DIR = Path('/data/codesign')
elif platform == util.Platform.WINDOWS:
SHARED_STORAGE_DIR = Path('Z:\\codesign')
elif platform == util.Platform.MACOS:
SHARED_STORAGE_DIR = Path('/Volumes/codesign_macos/codesign')
# https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema
LOGGING = {
'version': 1,
'formatters': {
'default': {'format': '%(asctime)-15s %(levelname)8s %(name)s %(message)s'}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'default',
'stream': 'ext://sys.stderr',
}
},
'loggers': {
'codesign': {'level': 'INFO'},
},
'root': {
'level': 'WARNING',
'handlers': [
'console',
],
}
}

View File

@@ -1,36 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
from pathlib import Path
# Timeout in seconds for the signing process.
#
# This is how long buildbot packing step will wait signing server to
# perform signing.
#
# NOTE: Notarization could take a long time, hence the rather high value
# here. Might consider using different timeout for different platforms.
TIMEOUT_IN_SECONDS = 45 * 60 * 60
# Directory which is shared across buildbot worker and signing server.
#
# This is where worker puts files requested for signing as well as where
# server puts signed files.
SHARED_STORAGE_DIR: Path

View File

@@ -1,101 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Configuration of a code signer which is specific to the code signing server.
#
# NOTE: DO NOT put any sensitive information here, put it in an actual
# configuration on the signing machine.
from pathlib import Path
from codesign.config_common import *
CODESIGN_DIRECTORY = Path(__file__).absolute().parent
BLENDER_GIT_ROOT_DIRECTORY = CODESIGN_DIRECTORY.parent.parent.parent
################################################################################
# Common configuration.
# Directory where folders for codesign requests and signed result are stored.
# For example, /data/codesign
SHARED_STORAGE_DIR: Path
################################################################################
# macOS-specific configuration.
MACOS_ENTITLEMENTS_FILE = \
BLENDER_GIT_ROOT_DIRECTORY / 'release' / 'darwin' / 'entitlements.plist'
# Identity of the Developer ID Application certificate which is to be used for
# codesign tool.
# Use `security find-identity -v -p codesigning` to find the identity.
#
# NOTE: This identity is just an example from release/darwin/README.txt.
MACOS_CODESIGN_IDENTITY = 'AE825E26F12D08B692F360133210AF46F4CF7B97'
# User name (Apple ID) which will be used to request notarization.
MACOS_XCRUN_USERNAME = 'me@example.com'
# One-time application password which will be used to request notarization.
MACOS_XCRUN_PASSWORD = '@keychain:altool-password'
# Timeout in seconds within which the notarial office is supposed to reply.
MACOS_NOTARIZE_TIMEOUT_IN_SECONDS = 60 * 60
################################################################################
# Windows-specific configuration.
# URL to the timestamping authority.
WIN_TIMESTAMP_AUTHORITY_URL = 'http://timestamp.digicert.com'
# Full path to the certificate used for signing.
#
# The path and expected file format might vary depending on a platform.
#
# On Windows it is usually is a PKCS #12 key (.pfx), so the path will look
# like Path('C:\\Secret\\Blender.pfx').
WIN_CERTIFICATE_FILEPATH: Path
################################################################################
# Logging configuration, common for all platforms.
# https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema
LOGGING = {
'version': 1,
'formatters': {
'default': {'format': '%(asctime)-15s %(levelname)8s %(name)s %(message)s'}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'default',
'stream': 'ext://sys.stderr',
}
},
'loggers': {
'codesign': {'level': 'INFO'},
},
'root': {
'level': 'WARNING',
'handlers': [
'console',
],
}
}

View File

@@ -1,26 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
class CodeSignException(Exception):
message: str
def __init__(self, message):
self.message = message
super().__init__(self.message)

View File

@@ -1,72 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# NOTE: This is a no-op signer (since there isn't really a procedure to sign
# Linux binaries yet). Used to debug and verify the code signing routines on
# a Linux environment.
import logging
from pathlib import Path
from typing import List
from codesign.absolute_and_relative_filename import AbsoluteAndRelativeFileName
from codesign.base_code_signer import BaseCodeSigner
logger = logging.getLogger(__name__)
logger_server = logger.getChild('server')
class LinuxCodeSigner(BaseCodeSigner):
def is_active(self) -> bool:
"""
Check whether this signer is active.
if it is inactive, no files will be signed.
Is used to be able to debug code signing pipeline on Linux, where there
is no code signing happening in the actual buildbot and release
environment.
"""
return False
def check_file_is_to_be_signed(
self, file: AbsoluteAndRelativeFileName) -> bool:
if file.relative_filepath == Path('blender'):
return True
if (file.relative_filepath.parts[-3:-1] == ('python', 'bin') and
file.relative_filepath.name.startwith('python')):
return True
if file.relative_filepath.suffix == '.so':
return True
return False
def collect_files_to_sign(self, path: Path) \
-> List[AbsoluteAndRelativeFileName]:
if not self.is_active():
return []
return super().collect_files_to_sign(path)
def sign_all_files(self, files: List[AbsoluteAndRelativeFileName]) -> None:
num_files = len(files)
for file_index, file in enumerate(files):
logger.info('Server: Signed file [%d/%d] %s',
file_index + 1, num_files, file.relative_filepath)

View File

@@ -1,456 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import logging
import re
import stat
import subprocess
import time
from pathlib import Path
from typing import List
import codesign.util as util
from buildbot_utils import Builder
from codesign.absolute_and_relative_filename import AbsoluteAndRelativeFileName
from codesign.base_code_signer import BaseCodeSigner
from codesign.exception import CodeSignException
logger = logging.getLogger(__name__)
logger_server = logger.getChild('server')
# NOTE: Check is done as filename.endswith(), so keep the dot
EXTENSIONS_TO_BE_SIGNED = {'.dylib', '.so', '.dmg'}
# Prefixes of a file (not directory) name which are to be signed.
# Used to sign extra executable files in Contents/Resources.
NAME_PREFIXES_TO_BE_SIGNED = {'python'}
class NotarizationException(CodeSignException):
pass
def is_file_from_bundle(file: AbsoluteAndRelativeFileName) -> bool:
"""
Check whether file is coming from an .app bundle
"""
parts = file.relative_filepath.parts
if not parts:
return False
if not parts[0].endswith('.app'):
return False
return True
def get_bundle_from_file(
file: AbsoluteAndRelativeFileName) -> AbsoluteAndRelativeFileName:
"""
Get AbsoluteAndRelativeFileName descriptor of bundle
"""
assert(is_file_from_bundle(file))
parts = file.relative_filepath.parts
bundle_name = parts[0]
base_dir = file.base_dir
bundle_filepath = file.base_dir / bundle_name
return AbsoluteAndRelativeFileName(base_dir, bundle_filepath)
def is_bundle_executable_file(file: AbsoluteAndRelativeFileName) -> bool:
"""
Check whether given file is an executable within an app bundle
"""
if not is_file_from_bundle(file):
return False
parts = file.relative_filepath.parts
num_parts = len(parts)
if num_parts < 3:
return False
if parts[1:3] != ('Contents', 'MacOS'):
return False
return True
def xcrun_field_value_from_output(field: str, output: str) -> str:
"""
Get value of a given field from xcrun output.
If field is not found empty string is returned.
"""
field_prefix = field + ': '
for line in output.splitlines():
line = line.strip()
if line.startswith(field_prefix):
return line[len(field_prefix):]
return ''
class MacOSCodeSigner(BaseCodeSigner):
def check_file_is_to_be_signed(
self, file: AbsoluteAndRelativeFileName) -> bool:
if file.relative_filepath.name.startswith('.'):
return False
if is_bundle_executable_file(file):
return True
base_name = file.relative_filepath.name
if any(base_name.startswith(prefix)
for prefix in NAME_PREFIXES_TO_BE_SIGNED):
return True
mode = file.absolute_filepath.lstat().st_mode
if mode & stat.S_IXUSR != 0:
file_output = subprocess.check_output(
("file", file.absolute_filepath)).decode()
if "64-bit executable" in file_output:
return True
return file.relative_filepath.suffix in EXTENSIONS_TO_BE_SIGNED
def collect_files_to_sign(self, path: Path) \
-> List[AbsoluteAndRelativeFileName]:
# Include all files when signing app or dmg bundle: all the files are
# needed to do valid signature of bundle.
if path.name.endswith('.app'):
return AbsoluteAndRelativeFileName.recursively_from_directory(path)
if path.is_dir():
files = []
for child in path.iterdir():
if child.name.endswith('.app'):
current_files = AbsoluteAndRelativeFileName.recursively_from_directory(
child)
else:
current_files = super().collect_files_to_sign(child)
for current_file in current_files:
files.append(AbsoluteAndRelativeFileName(
path, current_file.absolute_filepath))
return files
return super().collect_files_to_sign(path)
############################################################################
# Codesign.
def codesign_remove_signature(
self, file: AbsoluteAndRelativeFileName) -> None:
"""
Make sure given file does not have codesign signature
This is needed because codesigning is not possible for file which has
signature already.
"""
logger_server.info(
'Removing codesign signature from %s...', file.relative_filepath)
command = ['codesign', '--remove-signature', file.absolute_filepath]
self.run_command_or_mock(command, util.Platform.MACOS)
def codesign_file(
self, file: AbsoluteAndRelativeFileName) -> None:
"""
Sign given file
NOTE: File must not have any signatures.
"""
logger_server.info(
'Codesigning %s...', file.relative_filepath)
entitlements_file = self.config.MACOS_ENTITLEMENTS_FILE
command = ['codesign',
'--timestamp',
'--options', 'runtime',
f'--entitlements={entitlements_file}',
'--sign', self.config.MACOS_CODESIGN_IDENTITY,
file.absolute_filepath]
self.run_command_or_mock(command, util.Platform.MACOS)
def codesign_all_files(self, files: List[AbsoluteAndRelativeFileName]) -> None:
"""
Run codesign tool on all eligible files in the given list.
Will ignore all files which are not to be signed. For the rest will
remove possible existing signature and add a new signature.
"""
num_files = len(files)
have_ignored_files = False
signed_files = []
for file_index, file in enumerate(files):
# Ignore file if it is not to be signed.
# Allows to manually construct ZIP of a bundle and get it signed.
if not self.check_file_is_to_be_signed(file):
logger_server.info(
'Ignoring file [%d/%d] %s',
file_index + 1, num_files, file.relative_filepath)
have_ignored_files = True
continue
logger_server.info(
'Running codesigning routines for file [%d/%d] %s...',
file_index + 1, num_files, file.relative_filepath)
self.codesign_remove_signature(file)
self.codesign_file(file)
signed_files.append(file)
if have_ignored_files:
logger_server.info('Signed %d files:', len(signed_files))
num_signed_files = len(signed_files)
for file_index, signed_file in enumerate(signed_files):
logger_server.info(
'- [%d/%d] %s',
file_index + 1, num_signed_files,
signed_file.relative_filepath)
def codesign_bundles(
self, files: List[AbsoluteAndRelativeFileName]) -> None:
"""
Codesign all .app bundles in the given list of files.
Bundle is deducted from paths of the files, and every bundle is only
signed once.
"""
signed_bundles = set()
extra_files = []
for file in files:
if not is_file_from_bundle(file):
continue
bundle = get_bundle_from_file(file)
bundle_name = bundle.relative_filepath
if bundle_name in signed_bundles:
continue
logger_server.info('Running codesign routines on bundle %s',
bundle_name)
# It is not possible to remove signature from DMG.
if bundle.relative_filepath.name.endswith('.app'):
self.codesign_remove_signature(bundle)
self.codesign_file(bundle)
signed_bundles.add(bundle_name)
# Codesign on a bundle adds an extra folder with information.
# It needs to be compied to the source.
code_signature_directory = \
bundle.absolute_filepath / 'Contents' / '_CodeSignature'
code_signature_files = \
AbsoluteAndRelativeFileName.recursively_from_directory(
code_signature_directory)
for code_signature_file in code_signature_files:
bundle_relative_file = AbsoluteAndRelativeFileName(
bundle.base_dir,
code_signature_directory /
code_signature_file.relative_filepath)
extra_files.append(bundle_relative_file)
files.extend(extra_files)
############################################################################
# Notarization.
def notarize_get_bundle_id(self, file: AbsoluteAndRelativeFileName) -> str:
"""
Get bundle ID which will be used to notarize DMG
"""
name = file.relative_filepath.name
app_name = name.split('-', 2)[0].lower()
app_name_words = app_name.split()
if len(app_name_words) > 1:
app_name_id = ''.join(word.capitalize() for word in app_name_words)
else:
app_name_id = app_name_words[0]
# TODO(sergey): Consider using "alpha" for buildbot builds.
return f'org.blenderfoundation.{app_name_id}.release'
def notarize_request(self, file) -> str:
"""
Request notarization of the given file.
Returns UUID of the notarization request. If error occurred None is
returned instead of UUID.
"""
bundle_id = self.notarize_get_bundle_id(file)
logger_server.info('Bundle ID: %s', bundle_id)
logger_server.info('Submitting file to the notarial office.')
command = [
'xcrun', 'altool', '--notarize-app', '--verbose',
'-f', file.absolute_filepath,
'--primary-bundle-id', bundle_id,
'--username', self.config.MACOS_XCRUN_USERNAME,
'--password', self.config.MACOS_XCRUN_PASSWORD]
output = self.check_output_or_mock(
command, util.Platform.MACOS, allow_nonzero_exit_code=True)
for line in output.splitlines():
line = line.strip()
if line.startswith('RequestUUID = '):
request_uuid = line[14:]
return request_uuid
# Check whether the package has been already submitted.
if 'The software asset has already been uploaded.' in line:
request_uuid = re.sub(
'.*The upload ID is ([A-Fa-f0-9\-]+).*', '\\1', line)
logger_server.warning(
f'The package has been already submitted under UUID {request_uuid}')
return request_uuid
logger_server.error(output)
logger_server.error('xcrun command did not report RequestUUID')
return None
def notarize_review_status(self, xcrun_output: str) -> bool:
"""
Review status returned by xcrun's notarization info
Returns truth if the notarization process has finished.
If there are errors during notarization, a NotarizationException()
exception is thrown with status message from the notarial office.
"""
# Parse status and message
status = xcrun_field_value_from_output('Status', xcrun_output)
status_message = xcrun_field_value_from_output(
'Status Message', xcrun_output)
if status == 'success':
logger_server.info(
'Package successfully notarized: %s', status_message)
return True
if status == 'invalid':
logger_server.error(xcrun_output)
logger_server.error(
'Package notarization has failed: %s', status_message)
raise NotarizationException(status_message)
if status == 'in progress':
return False
logger_server.info(
'Unknown notarization status %s (%s)', status, status_message)
return False
def notarize_wait_result(self, request_uuid: str) -> None:
"""
Wait for until notarial office have a reply
"""
logger_server.info(
'Waiting for a result from the notarization office.')
command = ['xcrun', 'altool',
'--notarization-info', request_uuid,
'--username', self.config.MACOS_XCRUN_USERNAME,
'--password', self.config.MACOS_XCRUN_PASSWORD]
time_start = time.monotonic()
timeout_in_seconds = self.config.MACOS_NOTARIZE_TIMEOUT_IN_SECONDS
while True:
xcrun_output = self.check_output_or_mock(
command, util.Platform.MACOS, allow_nonzero_exit_code=True)
if self.notarize_review_status(xcrun_output):
break
logger_server.info('Keep waiting for notarization office.')
time.sleep(30)
time_slept_in_seconds = time.monotonic() - time_start
if time_slept_in_seconds > timeout_in_seconds:
logger_server.error(
"Notarial office didn't reply in %f seconds.",
timeout_in_seconds)
def notarize_staple(self, file: AbsoluteAndRelativeFileName) -> bool:
"""
Staple notarial label on the file
"""
logger_server.info('Stapling notarial stamp.')
command = ['xcrun', 'stapler', 'staple', '-v', file.absolute_filepath]
self.check_output_or_mock(command, util.Platform.MACOS)
def notarize_dmg(self, file: AbsoluteAndRelativeFileName) -> bool:
"""
Run entire pipeline to get DMG notarized.
"""
logger_server.info('Begin notarization routines on %s',
file.relative_filepath)
# Submit file for notarization.
request_uuid = self.notarize_request(file)
if not request_uuid:
return False
logger_server.info('Received Request UUID: %s', request_uuid)
# Wait for the status from the notarization office.
if not self.notarize_wait_result(request_uuid):
return False
# Staple.
self.notarize_staple(file)
def notarize_all_dmg(
self, files: List[AbsoluteAndRelativeFileName]) -> bool:
"""
Notarize all DMG images from the input.
Images are supposed to be codesigned already.
"""
for file in files:
if not file.relative_filepath.name.endswith('.dmg'):
continue
if not self.check_file_is_to_be_signed(file):
continue
self.notarize_dmg(file)
############################################################################
# Entry point.
def sign_all_files(self, files: List[AbsoluteAndRelativeFileName]) -> None:
# TODO(sergey): Handle errors somehow.
self.codesign_all_files(files)
self.codesign_bundles(files)
self.notarize_all_dmg(files)

View File

@@ -1,52 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import logging.config
import sys
from pathlib import Path
from typing import Optional
import codesign.config_builder
import codesign.util as util
from codesign.base_code_signer import BaseCodeSigner
class SimpleCodeSigner:
code_signer: Optional[BaseCodeSigner]
def __init__(self):
platform = util.get_current_platform()
if platform == util.Platform.LINUX:
from codesign.linux_code_signer import LinuxCodeSigner
self.code_signer = LinuxCodeSigner(codesign.config_builder)
elif platform == util.Platform.MACOS:
from codesign.macos_code_signer import MacOSCodeSigner
self.code_signer = MacOSCodeSigner(codesign.config_builder)
elif platform == util.Platform.WINDOWS:
from codesign.windows_code_signer import WindowsCodeSigner
self.code_signer = WindowsCodeSigner(codesign.config_builder)
else:
self.code_signer = None
def sign_file_or_directory(self, path: Path) -> None:
logging.config.dictConfig(codesign.config_builder.LOGGING)
self.code_signer.run_buildbot_path_sign_pipeline(path)

View File

@@ -1,54 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import sys
from enum import Enum
from pathlib import Path
class Platform(Enum):
LINUX = 1
MACOS = 2
WINDOWS = 3
def get_current_platform() -> Platform:
if sys.platform == 'linux':
return Platform.LINUX
elif sys.platform == 'darwin':
return Platform.MACOS
elif sys.platform == 'win32':
return Platform.WINDOWS
raise Exception(f'Unknown platform {sys.platform}')
def ensure_file_does_not_exist_or_die(filepath: Path) -> None:
"""
If the file exists, unlink it.
If the file path exists and is not a file an assert will trigger.
If the file path does not exists nothing happens.
"""
if not filepath.exists():
return
if not filepath.is_file():
# TODO(sergey): Provide information about what the filepath actually is.
raise SystemExit(f'{filepath} is expected to be a file, but is not')
filepath.unlink()

View File

@@ -1,117 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import logging
from pathlib import Path
from typing import List
import codesign.util as util
from buildbot_utils import Builder
from codesign.absolute_and_relative_filename import AbsoluteAndRelativeFileName
from codesign.base_code_signer import BaseCodeSigner
from codesign.exception import CodeSignException
logger = logging.getLogger(__name__)
logger_server = logger.getChild('server')
# NOTE: Check is done as filename.endswith(), so keep the dot
EXTENSIONS_TO_BE_SIGNED = {'.exe', '.dll', '.pyd', '.msi'}
BLACKLIST_FILE_PREFIXES = (
'api-ms-', 'concrt', 'msvcp', 'ucrtbase', 'vcomp', 'vcruntime')
class SigntoolException(CodeSignException):
pass
class WindowsCodeSigner(BaseCodeSigner):
def check_file_is_to_be_signed(
self, file: AbsoluteAndRelativeFileName) -> bool:
base_name = file.relative_filepath.name
if any(base_name.startswith(prefix)
for prefix in BLACKLIST_FILE_PREFIXES):
return False
return file.relative_filepath.suffix in EXTENSIONS_TO_BE_SIGNED
def get_sign_command_prefix(self) -> List[str]:
return [
'signtool', 'sign', '/v',
'/f', self.config.WIN_CERTIFICATE_FILEPATH,
'/tr', self.config.WIN_TIMESTAMP_AUTHORITY_URL]
def run_codesign_tool(self, filepath: Path) -> None:
command = self.get_sign_command_prefix() + [filepath]
try:
codesign_output = self.check_output_or_mock(command, util.Platform.WINDOWS)
except subprocess.CalledProcessError as e:
raise SigntoolException(f'Error running signtool {e}')
logger_server.info(f'signtool output:\n{codesign_output}')
got_number_of_success = False
for line in codesign_output.split('\n'):
line_clean = line.strip()
line_clean_lower = line_clean.lower()
if line_clean_lower.startswith('number of warnings') or \
line_clean_lower.startswith('number of errors'):
number = int(line_clean_lower.split(':')[1])
if number != 0:
raise SigntoolException('Non-clean success of signtool')
if line_clean_lower.startswith('number of files successfully signed'):
got_number_of_success = True
number = int(line_clean_lower.split(':')[1])
if number != 1:
raise SigntoolException('Signtool did not consider codesign a success')
if not got_number_of_success:
raise SigntoolException('Signtool did not report number of files signed')
def sign_all_files(self, files: List[AbsoluteAndRelativeFileName]) -> None:
# NOTE: Sign files one by one to avoid possible command line length
# overflow (which could happen if we ever decide to sign every binary
# in the install folder, for example).
#
# TODO(sergey): Consider doing batched signing of handful of files in
# one go (but only if this actually known to be much faster).
num_files = len(files)
for file_index, file in enumerate(files):
# Ignore file if it is not to be signed.
# Allows to manually construct ZIP of package and get it signed.
if not self.check_file_is_to_be_signed(file):
logger_server.info(
'Ignoring file [%d/%d] %s',
file_index + 1, num_files, file.relative_filepath)
continue
logger_server.info(
'Running signtool command for file [%d/%d] %s...',
file_index + 1, num_files, file.relative_filepath)
self.run_codesign_tool(file.absolute_filepath)

View File

@@ -1,37 +0,0 @@
#!/usr/bin/env python3
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# NOTE: This is a no-op signer (since there isn't really a procedure to sign
# Linux binaries yet). Used to debug and verify the code signing routines on
# a Linux environment.
import logging.config
from pathlib import Path
from typing import List
from codesign.linux_code_signer import LinuxCodeSigner
import codesign.config_server
if __name__ == "__main__":
logging.config.dictConfig(codesign.config_server.LOGGING)
code_signer = LinuxCodeSigner(codesign.config_server)
code_signer.run_signing_server()

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env python3
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import logging.config
from pathlib import Path
from typing import List
from codesign.macos_code_signer import MacOSCodeSigner
import codesign.config_server
if __name__ == "__main__":
entitlements_file = codesign.config_server.MACOS_ENTITLEMENTS_FILE
if not entitlements_file.exists():
raise SystemExit(
'Entitlements file {entitlements_file} does not exist.')
if not entitlements_file.is_file():
raise SystemExit(
'Entitlements file {entitlements_file} is not a file.')
logging.config.dictConfig(codesign.config_server.LOGGING)
code_signer = MacOSCodeSigner(codesign.config_server)
code_signer.run_signing_server()

View File

@@ -1,11 +0,0 @@
@echo off
rem This is an entry point of the codesign server for Windows.
rem It makes sure that signtool.exe is within the current PATH and can be
rem used by the Python script.
SETLOCAL
set PATH=C:\Program Files (x86)\Windows Kits\10\App Certification Kit;%PATH%
codesign_server_windows.py

View File

@@ -1,54 +0,0 @@
#!/usr/bin/env python3
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Implementation of codesign server for Windows.
#
# NOTE: If signtool.exe is not in the PATH use codesign_server_windows.bat
import logging.config
import shutil
from pathlib import Path
from typing import List
import codesign.util as util
from codesign.windows_code_signer import WindowsCodeSigner
import codesign.config_server
if __name__ == "__main__":
logging.config.dictConfig(codesign.config_server.LOGGING)
logger = logging.getLogger(__name__)
logger_server = logger.getChild('server')
# TODO(sergey): Consider moving such sanity checks into
# CodeSigner.check_environment_or_die().
if not shutil.which('signtool.exe'):
if util.get_current_platform() == util.Platform.WINDOWS:
raise SystemExit("signtool.exe is not found in %PATH%")
logger_server.info(
'signtool.exe not found, '
'but will not be used on this foreign platform')
code_signer = WindowsCodeSigner(codesign.config_server)
code_signer.run_signing_server()

View File

@@ -1,551 +0,0 @@
#!/usr/bin/env python3
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import argparse
import re
import shutil
import subprocess
import sys
import time
from pathlib import Path
from tempfile import TemporaryDirectory, NamedTemporaryFile
from typing import List
BUILDBOT_DIRECTORY = Path(__file__).absolute().parent
CODESIGN_SCRIPT = BUILDBOT_DIRECTORY / 'worker_codesign.py'
BLENDER_GIT_ROOT_DIRECTORY = BUILDBOT_DIRECTORY.parent.parent
DARWIN_DIRECTORY = BLENDER_GIT_ROOT_DIRECTORY / 'release' / 'darwin'
# Extra size which is added on top of actual files size when estimating size
# of destination DNG.
EXTRA_DMG_SIZE_IN_BYTES = 800 * 1024 * 1024
################################################################################
# Common utilities
def get_directory_size(root_directory: Path) -> int:
"""
Get size of directory on disk
"""
total_size = 0
for file in root_directory.glob('**/*'):
total_size += file.lstat().st_size
return total_size
################################################################################
# DMG bundling specific logic
def create_argument_parser():
parser = argparse.ArgumentParser()
parser.add_argument(
'source_dir',
type=Path,
help='Source directory which points to either existing .app bundle'
'or to a directory with .app bundles.')
parser.add_argument(
'--background-image',
type=Path,
help="Optional background picture which will be set on the DMG."
"If not provided default Blender's one is used.")
parser.add_argument(
'--volume-name',
type=str,
help='Optional name of a volume which will be used for DMG.')
parser.add_argument(
'--dmg',
type=Path,
help='Optional argument which points to a final DMG file name.')
parser.add_argument(
'--applescript',
type=Path,
help="Optional path to applescript to set up folder looks of DMG."
"If not provided default Blender's one is used.")
parser.add_argument(
'--codesign',
action="store_true",
help="Code sign and notarize DMG contents.")
return parser
def collect_app_bundles(source_dir: Path) -> List[Path]:
"""
Collect all app bundles which are to be put into DMG
If the source directory points to FOO.app it will be the only app bundle
packed.
Otherwise all .app bundles from given directory are placed to a single
DMG.
"""
if source_dir.name.endswith('.app'):
return [source_dir]
app_bundles = []
for filename in source_dir.glob('*'):
if not filename.is_dir():
continue
if not filename.name.endswith('.app'):
continue
app_bundles.append(filename)
return app_bundles
def collect_and_log_app_bundles(source_dir: Path) -> List[Path]:
app_bundles = collect_app_bundles(source_dir)
if not app_bundles:
print('No app bundles found for packing')
return
print(f'Found {len(app_bundles)} to pack:')
for app_bundle in app_bundles:
print(f'- {app_bundle}')
return app_bundles
def estimate_dmg_size(app_bundles: List[Path]) -> int:
"""
Estimate size of DMG to hold requested app bundles
The size is based on actual size of all files in all bundles plus some
space to compensate for different size-on-disk plus some space to hold
codesign signatures.
Is better to be on a high side since the empty space is compressed, but
lack of space might cause silent failures later on.
"""
app_bundles_size = 0
for app_bundle in app_bundles:
app_bundles_size += get_directory_size(app_bundle)
return app_bundles_size + EXTRA_DMG_SIZE_IN_BYTES
def copy_app_bundles_to_directory(app_bundles: List[Path],
directory: Path) -> None:
"""
Copy all bundles to a given directory
This directory is what the DMG will be created from.
"""
for app_bundle in app_bundles:
print(f'Copying {app_bundle.name}...')
shutil.copytree(app_bundle, directory / app_bundle.name)
def get_main_app_bundle(app_bundles: List[Path]) -> Path:
"""
Get application bundle main for the installation
"""
return app_bundles[0]
def create_dmg_image(app_bundles: List[Path],
dmg_filepath: Path,
volume_name: str) -> None:
"""
Create DMG disk image and put app bundles in it
No DMG configuration or codesigning is happening here.
"""
if dmg_filepath.exists():
print(f'Removing existing writable DMG {dmg_filepath}...')
dmg_filepath.unlink()
print('Preparing directory with app bundles for the DMG...')
with TemporaryDirectory(prefix='blender-dmg-content-') as content_dir_str:
# Copy all bundles to a clean directory.
content_dir = Path(content_dir_str)
copy_app_bundles_to_directory(app_bundles, content_dir)
# Estimate size of the DMG.
dmg_size = estimate_dmg_size(app_bundles)
print(f'Estimated DMG size: {dmg_size:,} bytes.')
# Create the DMG.
print(f'Creating writable DMG {dmg_filepath}')
command = ('hdiutil',
'create',
'-size', str(dmg_size),
'-fs', 'HFS+',
'-srcfolder', content_dir,
'-volname', volume_name,
'-format', 'UDRW',
dmg_filepath)
subprocess.run(command)
def get_writable_dmg_filepath(dmg_filepath: Path):
"""
Get file path for writable DMG image
"""
parent = dmg_filepath.parent
return parent / (dmg_filepath.stem + '-temp.dmg')
def mount_readwrite_dmg(dmg_filepath: Path) -> None:
"""
Mount writable DMG
Mounting point would be /Volumes/<volume name>
"""
print(f'Mounting read-write DMG ${dmg_filepath}')
command = ('hdiutil',
'attach', '-readwrite',
'-noverify',
'-noautoopen',
dmg_filepath)
subprocess.run(command)
def get_mount_directory_for_volume_name(volume_name: str) -> Path:
"""
Get directory under which the volume will be mounted
"""
return Path('/Volumes') / volume_name
def eject_volume(volume_name: str) -> None:
"""
Eject given volume, if mounted
"""
mount_directory = get_mount_directory_for_volume_name(volume_name)
if not mount_directory.exists():
return
mount_directory_str = str(mount_directory)
print(f'Ejecting volume {volume_name}')
# Figure out which device to eject.
mount_output = subprocess.check_output(['mount']).decode()
device = ''
for line in mount_output.splitlines():
if f'on {mount_directory_str} (' not in line:
continue
tokens = line.split(' ', 3)
if len(tokens) < 3:
continue
if tokens[1] != 'on':
continue
if device:
raise Exception(
f'Multiple devices found for mounting point {mount_directory}')
device = tokens[0]
if not device:
raise Exception(
f'No device found for mounting point {mount_directory}')
print(f'{mount_directory} is mounted as device {device}, ejecting...')
subprocess.run(['diskutil', 'eject', device])
def copy_background_if_needed(background_image_filepath: Path,
mount_directory: Path) -> None:
"""
Copy background to the DMG
If the background image is not specified it will not be copied.
"""
if not background_image_filepath:
print('No background image provided.')
return
print(f'Copying background image {background_image_filepath}')
destination_dir = mount_directory / '.background'
destination_dir.mkdir(exist_ok=True)
destination_filepath = destination_dir / background_image_filepath.name
shutil.copy(background_image_filepath, destination_filepath)
def create_applications_link(mount_directory: Path) -> None:
"""
Create link to /Applications in the given location
"""
print('Creating link to /Applications')
command = ('ln', '-s', '/Applications', mount_directory / ' ')
subprocess.run(command)
def run_applescript(applescript: Path,
volume_name: str,
app_bundles: List[Path],
background_image_filepath: Path) -> None:
"""
Run given applescript to adjust look and feel of the DMG
"""
main_app_bundle = get_main_app_bundle(app_bundles)
with NamedTemporaryFile(
mode='w', suffix='.applescript') as temp_applescript:
print('Adjusting applescript for volume name...')
# Adjust script to the specific volume name.
with open(applescript, mode='r') as input:
for line in input.readlines():
stripped_line = line.strip()
if stripped_line.startswith('tell disk'):
line = re.sub('tell disk ".*"',
f'tell disk "{volume_name}"',
line)
elif stripped_line.startswith('set background picture'):
if not background_image_filepath:
continue
else:
background_image_short = \
'.background:' + background_image_filepath.name
line = re.sub('to file ".*"',
f'to file "{background_image_short}"',
line)
line = line.replace('blender.app', main_app_bundle.name)
temp_applescript.write(line)
temp_applescript.flush()
print('Running applescript...')
command = ('osascript', temp_applescript.name)
subprocess.run(command)
print('Waiting for applescript...')
# NOTE: This is copied from bundle.sh. The exact reason for sleep is
# still remained a mystery.
time.sleep(5)
def codesign(subject: Path):
"""
Codesign file or directory
NOTE: For DMG it will also notarize.
"""
command = (CODESIGN_SCRIPT, subject)
subprocess.run(command)
def codesign_app_bundles_in_dmg(mount_directory: str) -> None:
"""
Code sign all binaries and bundles in the mounted directory
"""
print(f'Codesigning all app bundles in {mount_directory}')
codesign(mount_directory)
def codesign_and_notarize_dmg(dmg_filepath: Path) -> None:
"""
Run codesign and notarization pipeline on the DMG
"""
print(f'Codesigning and notarizing DMG {dmg_filepath}')
codesign(dmg_filepath)
def compress_dmg(writable_dmg_filepath: Path,
final_dmg_filepath: Path) -> None:
"""
Compress temporary read-write DMG
"""
command = ('hdiutil', 'convert',
writable_dmg_filepath,
'-format', 'UDZO',
'-o', final_dmg_filepath)
if final_dmg_filepath.exists():
print(f'Removing old compressed DMG {final_dmg_filepath}')
final_dmg_filepath.unlink()
print('Compressing disk image...')
subprocess.run(command)
def create_final_dmg(app_bundles: List[Path],
dmg_filepath: Path,
background_image_filepath: Path,
volume_name: str,
applescript: Path,
codesign: bool) -> None:
"""
Create DMG with all app bundles
Will take care configuring background, signing all binaries and app bundles
and notarizing the DMG.
"""
print('Running all routines to create final DMG')
writable_dmg_filepath = get_writable_dmg_filepath(dmg_filepath)
mount_directory = get_mount_directory_for_volume_name(volume_name)
# Make sure volume is not mounted.
# If it is mounted it will prevent removing old DMG files and could make
# it so app bundles are copied to the wrong place.
eject_volume(volume_name)
create_dmg_image(app_bundles, writable_dmg_filepath, volume_name)
mount_readwrite_dmg(writable_dmg_filepath)
# Run codesign first, prior to copying amything else.
#
# This allows to recurs into the content of bundles without worrying about
# possible interfereice of Application symlink.
if codesign:
codesign_app_bundles_in_dmg(mount_directory)
copy_background_if_needed(background_image_filepath, mount_directory)
create_applications_link(mount_directory)
run_applescript(applescript, volume_name, app_bundles,
background_image_filepath)
print('Ejecting read-write DMG image...')
eject_volume(volume_name)
compress_dmg(writable_dmg_filepath, dmg_filepath)
writable_dmg_filepath.unlink()
if codesign:
codesign_and_notarize_dmg(dmg_filepath)
def ensure_dmg_extension(filepath: Path) -> Path:
"""
Make sure given file have .dmg extension
"""
if filepath.suffix != '.dmg':
return filepath.with_suffix(f'{filepath.suffix}.dmg')
return filepath
def get_dmg_filepath(requested_name: Path, app_bundles: List[Path]) -> Path:
"""
Get full file path for the final DMG image
Will use the provided one when possible, otherwise will deduct it from
app bundles.
If the name is deducted, the DMG is stored in the current directory.
"""
if requested_name:
return ensure_dmg_extension(requested_name.absolute())
# TODO(sergey): This is not necessarily the main one.
main_bundle = app_bundles[0]
# Strip .app from the name
return Path(main_bundle.name[:-4] + '.dmg').absolute()
def get_background_image(requested_background_image: Path) -> Path:
"""
Get effective filepath for the background image
"""
if requested_background_image:
return requested_background_image.absolute()
return DARWIN_DIRECTORY / 'background.tif'
def get_applescript(requested_applescript: Path) -> Path:
"""
Get effective filepath for the applescript
"""
if requested_applescript:
return requested_applescript.absolute()
return DARWIN_DIRECTORY / 'blender.applescript'
def get_volume_name_from_dmg_filepath(dmg_filepath: Path) -> str:
"""
Deduct volume name from the DMG path
Will use first part of the DMG file name prior to dash.
"""
tokens = dmg_filepath.stem.split('-')
words = tokens[0].split()
return ' '.join(word.capitalize() for word in words)
def get_volume_name(requested_volume_name: str,
dmg_filepath: Path) -> str:
"""
Get effective name for DMG volume
"""
if requested_volume_name:
return requested_volume_name
return get_volume_name_from_dmg_filepath(dmg_filepath)
def main():
parser = create_argument_parser()
args = parser.parse_args()
# Get normalized input parameters.
source_dir = args.source_dir.absolute()
background_image_filepath = get_background_image(args.background_image)
applescript = get_applescript(args.applescript)
codesign = args.codesign
app_bundles = collect_and_log_app_bundles(source_dir)
if not app_bundles:
return
dmg_filepath = get_dmg_filepath(args.dmg, app_bundles)
volume_name = get_volume_name(args.volume_name, dmg_filepath)
print(f'Will produce DMG "{dmg_filepath.name}" (without quotes)')
create_final_dmg(app_bundles,
dmg_filepath,
background_image_filepath,
volume_name,
applescript,
codesign)
if __name__ == "__main__":
main()

View File

@@ -1,44 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# This is a script which is used as POST-INSTALL one for regular CMake's
# INSTALL target.
# It is used by buildbot workers to sign every binary which is going into
# the final buundle.
# On Windows Python 3 there only is python.exe, no python3.exe.
#
# On other platforms it is possible to have python2 and python3, and a
# symbolic link to python to either of them. So on those platforms use
# an explicit Python version.
if(WIN32)
set(PYTHON_EXECUTABLE python)
else()
set(PYTHON_EXECUTABLE python3)
endif()
execute_process(
COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/worker_codesign.py"
"${CMAKE_INSTALL_PREFIX}"
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
RESULT_VARIABLE exit_code
)
if(NOT exit_code EQUAL "0")
message(FATAL_ERROR "Non-zero exit code of codesign tool")
endif()

View File

@@ -1,74 +0,0 @@
#!/usr/bin/env python3
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# Helper script which takes care of signing provided location.
#
# The location can either be a directory (in which case all eligible binaries
# will be signed) or a single file (in which case a single file will be signed).
#
# This script takes care of all the complexity of communicating between process
# which requests file to be signed and the code signing server.
#
# NOTE: Signing happens in-place.
import argparse
import sys
from pathlib import Path
from codesign.simple_code_signer import SimpleCodeSigner
def create_argument_parser():
parser = argparse.ArgumentParser()
parser.add_argument('path_to_sign', type=Path)
return parser
def main():
parser = create_argument_parser()
args = parser.parse_args()
path_to_sign = args.path_to_sign.absolute()
if sys.platform == 'win32':
# When WIX packed is used to generate .msi on Windows the CPack will
# install two different projects and install them to different
# installation prefix:
#
# - C:\b\build\_CPack_Packages\WIX\Blender
# - C:\b\build\_CPack_Packages\WIX\Unspecified
#
# Annoying part is: CMake's post-install script will only be run
# once, with the install prefix which corresponds to a project which
# was installed last. But we want to sign binaries from all projects.
# So in order to do so we detect that we are running for a CPack's
# project used for WIX and force parent directory (which includes both
# projects) to be signed.
#
# Here we force both projects to be signed.
if path_to_sign.name == 'Unspecified' and 'WIX' in str(path_to_sign):
path_to_sign = path_to_sign.parent
code_signer = SimpleCodeSigner()
code_signer.sign_file_or_directory(path_to_sign)
if __name__ == "__main__":
main()

View File

@@ -1,135 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import os
import shutil
import buildbot_utils
def get_cmake_options(builder):
codesign_script = os.path.join(
builder.blender_dir, 'build_files', 'buildbot', 'worker_codesign.cmake')
config_file = "build_files/cmake/config/blender_release.cmake"
options = ['-DCMAKE_BUILD_TYPE:STRING=Release',
'-DWITH_GTESTS=ON']
if builder.platform == 'mac':
options.append('-DCMAKE_OSX_ARCHITECTURES:STRING=x86_64')
options.append('-DCMAKE_OSX_DEPLOYMENT_TARGET=10.9')
elif builder.platform == 'win':
options.extend(['-G', 'Visual Studio 16 2019', '-A', 'x64'])
if builder.codesign:
options.extend(['-DPOSTINSTALL_SCRIPT:PATH=' + codesign_script])
elif builder.platform == 'linux':
config_file = "build_files/buildbot/config/blender_linux.cmake"
optix_sdk_dir = os.path.join(builder.blender_dir, '..', '..', 'NVIDIA-Optix-SDK-7.1')
options.append('-DOPTIX_ROOT_DIR:PATH=' + optix_sdk_dir)
# Workaround to build sm_30 kernels with CUDA 10, since CUDA 11 no longer supports that architecture
if builder.platform == 'win':
options.append('-DCUDA10_TOOLKIT_ROOT_DIR:PATH=C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1')
options.append('-DCUDA10_NVCC_EXECUTABLE:FILEPATH=C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1/bin/nvcc.exe')
options.append('-DCUDA11_TOOLKIT_ROOT_DIR:PATH=C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.1')
options.append('-DCUDA11_NVCC_EXECUTABLE:FILEPATH=C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.1/bin/nvcc.exe')
elif builder.platform == 'linux':
options.append('-DCUDA10_TOOLKIT_ROOT_DIR:PATH=/usr/local/cuda-10.1')
options.append('-DCUDA10_NVCC_EXECUTABLE:FILEPATH=/usr/local/cuda-10.1/bin/nvcc')
options.append('-DCUDA11_TOOLKIT_ROOT_DIR:PATH=/usr/local/cuda-11.1')
options.append('-DCUDA11_NVCC_EXECUTABLE:FILEPATH=/usr/local/cuda-11.1/bin/nvcc')
options.append("-C" + os.path.join(builder.blender_dir, config_file))
options.append("-DCMAKE_INSTALL_PREFIX=%s" % (builder.install_dir))
return options
def update_git(builder):
# Do extra git fetch because not all platform/git/buildbot combinations
# update the origin remote, causing buildinfo to detect local changes.
os.chdir(builder.blender_dir)
print("Fetching remotes")
command = ['git', 'fetch', '--all']
buildbot_utils.call(builder.command_prefix + command)
def clean_directories(builder):
# Make sure no garbage remained from the previous run
if os.path.isdir(builder.install_dir):
shutil.rmtree(builder.install_dir)
# Make sure build directory exists and enter it
os.makedirs(builder.build_dir, exist_ok=True)
# Remove buildinfo files to force buildbot to re-generate them.
for buildinfo in ('buildinfo.h', 'buildinfo.h.txt', ):
full_path = os.path.join(builder.build_dir, 'source', 'creator', buildinfo)
if os.path.exists(full_path):
print("Removing {}" . format(buildinfo))
os.remove(full_path)
def cmake_configure(builder):
# CMake configuration
os.chdir(builder.build_dir)
cmake_cache = os.path.join(builder.build_dir, 'CMakeCache.txt')
if os.path.exists(cmake_cache):
print("Removing CMake cache")
os.remove(cmake_cache)
print("CMake configure:")
cmake_options = get_cmake_options(builder)
command = ['cmake', builder.blender_dir] + cmake_options
buildbot_utils.call(builder.command_prefix + command)
def cmake_build(builder):
# CMake build
os.chdir(builder.build_dir)
# NOTE: CPack will build an INSTALL target, which would mean that code
# signing will happen twice when using `make install` and CPack.
# The tricky bit here is that it is not possible to know whether INSTALL
# target is used by CPack or by a buildbot itaself. Extra level on top of
# this is that on Windows it is required to build INSTALL target in order
# to have unit test binaries to run.
# So on the one hand we do an extra unneeded code sign on Windows, but on
# a positive side we don't add complexity and don't make build process more
# fragile trying to avoid this. The signing process is way faster than just
# a clean build of buildbot, especially with regression tests enabled.
if builder.platform == 'win':
command = ['cmake', '--build', '.', '--target', 'install', '--config', 'Release']
else:
command = ['make', '-s', '-j16', 'install']
print("CMake build:")
buildbot_utils.call(builder.command_prefix + command)
if __name__ == "__main__":
builder = buildbot_utils.create_builder_from_arguments()
update_git(builder)
clean_directories(builder)
cmake_configure(builder)
cmake_build(builder)

View File

@@ -1,208 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Runs on buildbot worker, creating a release package using the build
# system and zipping it into buildbot_upload.zip. This is then uploaded
# to the master in the next buildbot step.
import os
import sys
from pathlib import Path
import buildbot_utils
def get_package_name(builder, platform=None):
info = buildbot_utils.VersionInfo(builder)
package_name = 'blender-' + info.full_version
if platform:
package_name += '-' + platform
if not (builder.branch == 'master' or builder.is_release_branch):
if info.is_development_build:
package_name = builder.branch + "-" + package_name
return package_name
def sign_file_or_directory(path):
from codesign.simple_code_signer import SimpleCodeSigner
code_signer = SimpleCodeSigner()
code_signer.sign_file_or_directory(Path(path))
def create_buildbot_upload_zip(builder, package_files):
import zipfile
buildbot_upload_zip = os.path.join(builder.upload_dir, "buildbot_upload.zip")
if os.path.exists(buildbot_upload_zip):
os.remove(buildbot_upload_zip)
try:
z = zipfile.ZipFile(buildbot_upload_zip, "w", compression=zipfile.ZIP_STORED)
for filepath, filename in package_files:
print("Packaged", filename)
z.write(filepath, arcname=filename)
z.close()
except Exception as ex:
sys.stderr.write('Create buildbot_upload.zip failed: ' + str(ex) + '\n')
sys.exit(1)
def create_tar_xz(src, dest, package_name):
# One extra to remove leading os.sep when cleaning root for package_root
ln = len(src) + 1
flist = list()
# Create list of tuples containing file and archive name
for root, dirs, files in os.walk(src):
package_root = os.path.join(package_name, root[ln:])
flist.extend([(os.path.join(root, file), os.path.join(package_root, file)) for file in files])
import tarfile
# Set UID/GID of archived files to 0, otherwise they'd be owned by whatever
# user compiled the package. If root then unpacks it to /usr/local/ you get
# a security issue.
def _fakeroot(tarinfo):
tarinfo.gid = 0
tarinfo.gname = "root"
tarinfo.uid = 0
tarinfo.uname = "root"
return tarinfo
package = tarfile.open(dest, 'w:xz', preset=9)
for entry in flist:
package.add(entry[0], entry[1], recursive=False, filter=_fakeroot)
package.close()
def cleanup_files(dirpath, extension):
for f in os.listdir(dirpath):
filepath = os.path.join(dirpath, f)
if os.path.isfile(filepath) and f.endswith(extension):
os.remove(filepath)
def pack_mac(builder):
info = buildbot_utils.VersionInfo(builder)
os.chdir(builder.build_dir)
cleanup_files(builder.build_dir, '.dmg')
package_name = get_package_name(builder, 'macOS')
package_filename = package_name + '.dmg'
package_filepath = os.path.join(builder.build_dir, package_filename)
release_dir = os.path.join(builder.blender_dir, 'release', 'darwin')
buildbot_dir = os.path.join(builder.blender_dir, 'build_files', 'buildbot')
bundle_script = os.path.join(buildbot_dir, 'worker_bundle_dmg.py')
command = [bundle_script]
command += ['--dmg', package_filepath]
if info.is_development_build:
background_image = os.path.join(release_dir, 'buildbot', 'background.tif')
command += ['--background-image', background_image]
if builder.codesign:
command += ['--codesign']
command += [builder.install_dir]
buildbot_utils.call(command)
create_buildbot_upload_zip(builder, [(package_filepath, package_filename)])
def pack_win(builder):
info = buildbot_utils.VersionInfo(builder)
os.chdir(builder.build_dir)
cleanup_files(builder.build_dir, '.zip')
# CPack will add the platform name
cpack_name = get_package_name(builder, None)
package_name = get_package_name(builder, 'windows' + str(builder.bits))
command = ['cmake', '-DCPACK_OVERRIDE_PACKAGENAME:STRING=' + cpack_name, '.']
buildbot_utils.call(builder.command_prefix + command)
command = ['cpack', '-G', 'ZIP']
buildbot_utils.call(builder.command_prefix + command)
package_filename = package_name + '.zip'
package_filepath = os.path.join(builder.build_dir, package_filename)
package_files = [(package_filepath, package_filename)]
if info.version_cycle == 'release':
# Installer only for final release builds, otherwise will get
# 'this product is already installed' messages.
command = ['cpack', '-G', 'WIX']
buildbot_utils.call(builder.command_prefix + command)
package_filename = package_name + '.msi'
package_filepath = os.path.join(builder.build_dir, package_filename)
if builder.codesign:
sign_file_or_directory(package_filepath)
package_files += [(package_filepath, package_filename)]
create_buildbot_upload_zip(builder, package_files)
def pack_linux(builder):
blender_executable = os.path.join(builder.install_dir, 'blender')
info = buildbot_utils.VersionInfo(builder)
# Strip all unused symbols from the binaries
print("Stripping binaries...")
buildbot_utils.call(builder.command_prefix + ['strip', '--strip-all', blender_executable])
print("Stripping python...")
py_target = os.path.join(builder.install_dir, info.short_version)
buildbot_utils.call(
builder.command_prefix + [
'find', py_target, '-iname', '*.so', '-exec', 'strip', '-s', '{}', ';',
],
)
# Construct package name
platform_name = 'linux64'
package_name = get_package_name(builder, platform_name)
package_filename = package_name + ".tar.xz"
print("Creating .tar.xz archive")
package_filepath = builder.install_dir + '.tar.xz'
create_tar_xz(builder.install_dir, package_filepath, package_name)
# Create buildbot_upload.zip
create_buildbot_upload_zip(builder, [(package_filepath, package_filename)])
if __name__ == "__main__":
builder = buildbot_utils.create_builder_from_arguments()
# Make sure install directory always exists
os.makedirs(builder.install_dir, exist_ok=True)
if builder.platform == 'mac':
pack_mac(builder)
elif builder.platform == 'win':
pack_win(builder)
elif builder.platform == 'linux':
pack_linux(builder)

View File

@@ -1,42 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import buildbot_utils
import os
import sys
def get_ctest_arguments(builder):
args = ['--output-on-failure']
if builder.platform == 'win':
args += ['-C', 'Release']
return args
def test(builder):
os.chdir(builder.build_dir)
command = builder.command_prefix + ['ctest'] + get_ctest_arguments(builder)
buildbot_utils.call(command)
if __name__ == "__main__":
builder = buildbot_utils.create_builder_from_arguments()
test(builder)

View File

@@ -1,31 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import buildbot_utils
import os
import sys
if __name__ == "__main__":
builder = buildbot_utils.create_builder_from_arguments()
os.chdir(builder.blender_dir)
# Run make update which handles all libraries and submodules.
make_update = os.path.join(builder.blender_dir, "build_files", "utils", "make_update.py")
buildbot_utils.call([sys.executable, make_update, '--no-blender', "--use-tests", "--use-centos-libraries"])

View File

@@ -56,10 +56,6 @@ set(WITH_TBB ON CACHE BOOL "" FORCE)
set(WITH_USD ON CACHE BOOL "" FORCE)
set(WITH_MEM_JEMALLOC ON CACHE BOOL "" FORCE)
set(WITH_CYCLES_CUDA_BINARIES ON CACHE BOOL "" FORCE)
set(WITH_CYCLES_CUBIN_COMPILER OFF CACHE BOOL "" FORCE)
set(CYCLES_CUDA_BINARIES_ARCH sm_30;sm_35;sm_37;sm_50;sm_52;sm_60;sm_61;sm_70;sm_75;sm_86;compute_75 CACHE STRING "" FORCE)
set(WITH_CYCLES_DEVICE_OPTIX ON CACHE BOOL "" FORCE)
# platform dependent options
if(APPLE)
@@ -80,4 +76,8 @@ if(UNIX AND NOT APPLE)
endif()
if(NOT APPLE)
set(WITH_XR_OPENXR ON CACHE BOOL "" FORCE)
set(WITH_CYCLES_DEVICE_OPTIX ON CACHE BOOL "" FORCE)
set(WITH_CYCLES_CUDA_BINARIES ON CACHE BOOL "" FORCE)
set(WITH_CYCLES_CUBIN_COMPILER OFF CACHE BOOL "" FORCE)
endif()

View File

@@ -104,8 +104,8 @@ if(WIN32)
set(CPACK_WIX_LIGHT_EXTRA_FLAGS -dcl:medium)
endif()
set(CPACK_PACKAGE_EXECUTABLES "blender" "blender")
set(CPACK_CREATE_DESKTOP_LINKS "blender" "blender")
set(CPACK_PACKAGE_EXECUTABLES "blender-launcher" "blender")
set(CPACK_CREATE_DESKTOP_LINKS "blender-launcher" "blender")
include(CPack)

View File

@@ -119,7 +119,7 @@ string(APPEND CMAKE_MODULE_LINKER_FLAGS " /SAFESEH:NO /ignore:4099")
list(APPEND PLATFORM_LINKLIBS
ws2_32 vfw32 winmm kernel32 user32 gdi32 comdlg32 Comctl32 version
advapi32 shfolder shell32 ole32 oleaut32 uuid psapi Dbghelp Shlwapi
pathcch
pathcch Shcore
)
if(WITH_INPUT_IME)
@@ -144,8 +144,8 @@ add_definitions(-D_ALLOW_KEYWORD_MACROS)
# that both /GR and /GR- are specified.
remove_cc_flag("/GR")
# We want to support Windows 7 level ABI
add_definitions(-D_WIN32_WINNT=0x601)
# Make the Windows 8.1 API available for use.
add_definitions(-D_WIN32_WINNT=0x603)
include(build_files/cmake/platform/platform_win32_bundle_crt.cmake)
remove_cc_flag("/MDd" "/MD" "/Zi")

View File

@@ -0,0 +1,8 @@
Pipeline Config
===============
This configuration file is used by buildbot new pipeline for the `update-code` step.
It will soon be used by the ../utils/make_update.py script.
Both buildbot and developers will eventually use the same configuration file.

View File

@@ -0,0 +1,87 @@
{
"update-code":
{
"git" :
{
"submodules":
[
{ "path": "release/scripts/addons", "branch": "master", "commit_id": "HEAD" },
{ "path": "release/scripts/addons_contrib", "branch": "master", "commit_id": "HEAD" },
{ "path": "release/datafiles/locale", "branch": "master", "commit_id": "HEAD" },
{ "path": "source/tools", "branch": "master", "commit_id": "HEAD" }
]
},
"svn":
{
"tests": { "path": "lib/tests", "branch": "trunk", "commit_id": "HEAD" },
"libraries":
{
"darwin-x86_64": { "path": "lib/darwin", "branch": "trunk", "commit_id": "HEAD" },
"darwin-arm64": { "path": "lib/darwin_arm64", "branch": "trunk", "commit_id": "HEAD" },
"linux-x86_64": { "path": "lib/linux_centos7_x86_64", "branch": "trunk", "commit_id": "HEAD" },
"windows-amd64": { "path": "lib/win64_vc15", "branch": "trunk", "commit_id": "HEAD" }
}
}
},
"buildbot":
{
"gcc":
{
"version": "9.0"
},
"sdks":
{
"optix":
{
"version": "7.1.0"
},
"cuda10":
{
"version": "10.1"
},
"cuda11":
{
"version": "11.3"
}
},
"cmake":
{
"default":
{
"version": "any",
"overrides":
{
}
},
"darwin-x86_64":
{
"overrides":
{
}
},
"darwin-arm64":
{
"overrides":
{
}
},
"linux-x86_64":
{
"overrides":
{
}
},
"windows-amd64":
{
"overrides":
{
}
}
}
}
}

View File

@@ -0,0 +1,5 @@
Make Utility Scripts
====================
Scripts used only by developers for now

View File

@@ -85,7 +85,7 @@ def openBlendFile(filename):
'''
handle = open(filename, 'rb')
magic = ReadString(handle, 7)
if magic in ("BLENDER", "BULLETf"):
if magic in {"BLENDER", "BULLETf"}:
log.debug("normal blendfile detected")
handle.seek(0, os.SEEK_SET)
return handle
@@ -137,7 +137,7 @@ class BlendFile:
fileblock = BlendFileBlock(handle, self)
found_dna_block = False
while not found_dna_block:
if fileblock.Header.Code in ("DNA1", "SDNA"):
if fileblock.Header.Code in {"DNA1", "SDNA"}:
self.Catalog = DNACatalog(self.Header, handle)
found_dna_block = True
else:

View File

@@ -954,7 +954,7 @@ def pymodule2sphinx(basepath, module_name, module, title, module_all_extra):
# constant, not much fun we can do here except to list it.
# TODO, figure out some way to document these!
fw(".. data:: %s\n\n" % attribute)
write_indented_lines(" ", fw, "constant value %s" % repr(value), False)
write_indented_lines(" ", fw, "Constant value %s" % repr(value), False)
fw("\n")
else:
BPY_LOGGER.debug("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
@@ -1246,7 +1246,7 @@ def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
"%s.\n" % (
identifier,
# Account for multi-line enum descriptions, allowing this to be a block of text.
indent(", ".join(escape_rst(val) for val in (name, description) if val) or "Undocumented", " "),
indent(" -- ".join(escape_rst(val) for val in (name, description) if val) or "Undocumented", " "),
)
for identifier, name, description in prop.enum_items
])

View File

@@ -75,6 +75,7 @@ void FFMPEGWriter::encode()
m_frame->nb_samples = m_input_samples;
m_frame->format = m_codecCtx->sample_fmt;
m_frame->channel_layout = m_codecCtx->channel_layout;
m_frame->channels = m_specs.channels;
if(avcodec_fill_audio_frame(m_frame, m_specs.channels, m_codecCtx->sample_fmt, reinterpret_cast<data_t*>(data), m_input_buffer.getSize(), 0) < 0)
AUD_THROW(FileException, "File couldn't be written, filling the audio frame failed with ffmpeg.");

View File

@@ -83,6 +83,8 @@ struct BlenderCamera {
BoundBox2D pano_viewplane;
BoundBox2D viewport_camera_border;
float passepartout_alpha;
Transform matrix;
float offscreen_dicing_scale;
@@ -125,6 +127,7 @@ static void blender_camera_init(BlenderCamera *bcam, BL::RenderSettings &b_rende
bcam->pano_viewplane.top = 1.0f;
bcam->viewport_camera_border.right = 1.0f;
bcam->viewport_camera_border.top = 1.0f;
bcam->passepartout_alpha = 0.5f;
bcam->offscreen_dicing_scale = 1.0f;
bcam->matrix = transform_identity();
@@ -212,6 +215,8 @@ static void blender_camera_from_object(BlenderCamera *bcam,
bcam->lens = b_camera.lens();
bcam->passepartout_alpha = b_camera.show_passepartout() ? b_camera.passepartout_alpha() : 0.0f;
if (b_camera.dof().use_dof()) {
/* allow f/stop number to change aperture_size but still
* give manual control over aperture radius */
@@ -834,15 +839,19 @@ static void blender_camera_border(BlenderCamera *bcam,
full_border,
&bcam->viewport_camera_border);
if (!b_render.use_border()) {
if (b_render.use_border()) {
bcam->border.left = b_render.border_min_x();
bcam->border.right = b_render.border_max_x();
bcam->border.bottom = b_render.border_min_y();
bcam->border.top = b_render.border_max_y();
}
else if (bcam->passepartout_alpha == 1.0f) {
bcam->border = full_border;
}
else {
return;
}
bcam->border.left = b_render.border_min_x();
bcam->border.right = b_render.border_max_x();
bcam->border.bottom = b_render.border_min_y();
bcam->border.top = b_render.border_max_y();
/* Determine viewport subset matching camera border. */
blender_camera_border_subset(b_engine,
b_render,
@@ -885,8 +894,7 @@ void BlenderSync::sync_view(BL::SpaceView3D &b_v3d,
}
}
BufferParams BlenderSync::get_buffer_params(BL::RenderSettings &b_render,
BL::SpaceView3D &b_v3d,
BufferParams BlenderSync::get_buffer_params(BL::SpaceView3D &b_v3d,
BL::RegionView3D &b_rv3d,
Camera *cam,
int width,
@@ -902,7 +910,8 @@ BufferParams BlenderSync::get_buffer_params(BL::RenderSettings &b_render,
if (b_v3d && b_rv3d && b_rv3d.view_perspective() != BL::RegionView3D::view_perspective_CAMERA)
use_border = b_v3d.use_render_border();
else
use_border = b_render.use_border();
/* the camera can always have a passepartout */
use_border = true;
if (use_border) {
/* border render */

View File

@@ -155,7 +155,7 @@ void BlenderSession::create_session()
/* set buffer parameters */
BufferParams buffer_params = BlenderSync::get_buffer_params(
b_render, b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
session->reset(buffer_params, session_params.samples);
b_engine.use_highlight_tiles(session_params.progressive_refine == false);
@@ -242,8 +242,7 @@ void BlenderSession::reset_session(BL::BlendData &b_data, BL::Depsgraph &b_depsg
BL::SpaceView3D b_null_space_view3d(PointerRNA_NULL);
BL::RegionView3D b_null_region_view3d(PointerRNA_NULL);
BufferParams buffer_params = BlenderSync::get_buffer_params(b_render,
b_null_space_view3d,
BufferParams buffer_params = BlenderSync::get_buffer_params(b_null_space_view3d,
b_null_region_view3d,
scene->camera,
width,
@@ -486,7 +485,7 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
SessionParams session_params = BlenderSync::get_session_params(
b_engine, b_userpref, b_scene, background, b_view_layer);
BufferParams buffer_params = BlenderSync::get_buffer_params(
b_render, b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
/* temporary render result to find needed passes and views */
BL::RenderResult b_rr = begin_render_result(
@@ -810,7 +809,7 @@ void BlenderSession::synchronize(BL::Depsgraph &b_depsgraph_)
/* get buffer parameters */
BufferParams buffer_params = BlenderSync::get_buffer_params(
b_render, b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
if (!buffer_params.denoising_data_pass) {
session_params.denoising.use = false;
@@ -889,7 +888,7 @@ bool BlenderSession::draw(int w, int h)
SessionParams session_params = BlenderSync::get_session_params(
b_engine, b_userpref, b_scene, background);
BufferParams buffer_params = BlenderSync::get_buffer_params(
b_render, b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
b_v3d, b_rv3d, scene->camera, width, height, session_params.denoising.use);
bool session_pause = BlenderSync::get_session_pause(b_scene, background);
if (session_pause == false) {
@@ -907,7 +906,7 @@ bool BlenderSession::draw(int w, int h)
/* draw */
BufferParams buffer_params = BlenderSync::get_buffer_params(
b_render, b_v3d, b_rv3d, scene->camera, width, height, session->params.denoising.use);
b_v3d, b_rv3d, scene->camera, width, height, session->params.denoising.use);
DeviceDrawParams draw_params;
if (session->params.display_buffer_linear) {

View File

@@ -1373,7 +1373,7 @@ void BlenderSync::sync_world(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d,
BlenderViewportParameters new_viewport_parameters(b_v3d);
if (world_recalc || update_all || b_world.ptr.data != world_map ||
viewport_parameters.modified(new_viewport_parameters)) {
viewport_parameters.shader_modified(new_viewport_parameters)) {
Shader *shader = scene->default_background;
ShaderGraph *graph = new ShaderGraph();
@@ -1501,8 +1501,8 @@ void BlenderSync::sync_world(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d,
background->set_transparent_roughness_threshold(0.0f);
}
background->set_use_shader(view_layer.use_background_shader |
viewport_parameters.custom_viewport_parameters());
background->set_use_shader(view_layer.use_background_shader ||
viewport_parameters.use_custom_shader());
background->set_use_ao(background->get_use_ao() && view_layer.use_background_ao);
background->tag_update(scene);

View File

@@ -224,9 +224,13 @@ void BlenderSync::sync_recalc(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d
if (b_v3d) {
BlenderViewportParameters new_viewport_parameters(b_v3d);
if (viewport_parameters.modified(new_viewport_parameters)) {
if (viewport_parameters.shader_modified(new_viewport_parameters)) {
world_recalc = true;
has_updates_ = true;
}
has_updates_ |= viewport_parameters.modified(new_viewport_parameters);
}
}
@@ -246,7 +250,7 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render,
BL::ViewLayer b_view_layer = b_depsgraph.view_layer_eval();
sync_view_layer(b_v3d, b_view_layer);
sync_view_layer(b_view_layer);
sync_integrator();
sync_film(b_v3d);
sync_shaders(b_depsgraph, b_v3d);
@@ -441,7 +445,7 @@ void BlenderSync::sync_film(BL::SpaceView3D &b_v3d)
/* Render Layer */
void BlenderSync::sync_view_layer(BL::SpaceView3D & /*b_v3d*/, BL::ViewLayer &b_view_layer)
void BlenderSync::sync_view_layer(BL::ViewLayer &b_view_layer)
{
view_layer.name = b_view_layer.name();

View File

@@ -73,7 +73,7 @@ class BlenderSync {
int width,
int height,
void **python_thread_state);
void sync_view_layer(BL::SpaceView3D &b_v3d, BL::ViewLayer &b_view_layer);
void sync_view_layer(BL::ViewLayer &b_view_layer);
vector<Pass> sync_render_passes(BL::Scene &b_scene,
BL::RenderLayer &b_render_layer,
BL::ViewLayer &b_view_layer,
@@ -104,8 +104,7 @@ class BlenderSync {
bool background,
BL::ViewLayer b_view_layer = BL::ViewLayer(PointerRNA_NULL));
static bool get_session_pause(BL::Scene &b_scene, bool background);
static BufferParams get_buffer_params(BL::RenderSettings &b_render,
BL::SpaceView3D &b_v3d,
static BufferParams get_buffer_params(BL::SpaceView3D &b_v3d,
BL::RegionView3D &b_rv3d,
Camera *cam,
int width,

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "blender_viewport.h"
#include "blender_util.h"
@@ -25,29 +26,39 @@ BlenderViewportParameters::BlenderViewportParameters()
studiolight_rotate_z(0.0f),
studiolight_intensity(1.0f),
studiolight_background_alpha(1.0f),
studiolight_path(ustring())
display_pass(PASS_COMBINED)
{
}
BlenderViewportParameters::BlenderViewportParameters(BL::SpaceView3D &b_v3d)
: BlenderViewportParameters()
{
/* We only copy the parameters if we are in look dev mode. otherwise
if (!b_v3d) {
return;
}
BL::View3DShading shading = b_v3d.shading();
PointerRNA cshading = RNA_pointer_get(&shading.ptr, "cycles");
/* We only copy the shading parameters if we are in look dev mode. otherwise
* defaults are being used. These defaults mimic normal render settings */
if (b_v3d && b_v3d.shading().type() == BL::View3DShading::type_RENDERED) {
use_scene_world = b_v3d.shading().use_scene_world_render();
use_scene_lights = b_v3d.shading().use_scene_lights_render();
if (shading.type() == BL::View3DShading::type_RENDERED) {
use_scene_world = shading.use_scene_world_render();
use_scene_lights = shading.use_scene_lights_render();
if (!use_scene_world) {
studiolight_rotate_z = b_v3d.shading().studiolight_rotate_z();
studiolight_intensity = b_v3d.shading().studiolight_intensity();
studiolight_background_alpha = b_v3d.shading().studiolight_background_alpha();
studiolight_path = b_v3d.shading().selected_studio_light().path();
studiolight_rotate_z = shading.studiolight_rotate_z();
studiolight_intensity = shading.studiolight_intensity();
studiolight_background_alpha = shading.studiolight_background_alpha();
studiolight_path = shading.selected_studio_light().path();
}
}
/* Film. */
display_pass = (PassType)get_enum(cshading, "render_pass", -1, -1);
}
/* Check if two instances are different. */
const bool BlenderViewportParameters::modified(const BlenderViewportParameters &other) const
bool BlenderViewportParameters::shader_modified(const BlenderViewportParameters &other) const
{
return use_scene_world != other.use_scene_world || use_scene_lights != other.use_scene_lights ||
studiolight_rotate_z != other.studiolight_rotate_z ||
@@ -56,26 +67,26 @@ const bool BlenderViewportParameters::modified(const BlenderViewportParameters &
studiolight_path != other.studiolight_path;
}
const bool BlenderViewportParameters::custom_viewport_parameters() const
bool BlenderViewportParameters::film_modified(const BlenderViewportParameters &other) const
{
return !(use_scene_world && use_scene_lights);
return display_pass != other.display_pass;
}
PassType BlenderViewportParameters::get_viewport_display_render_pass(BL::SpaceView3D &b_v3d)
bool BlenderViewportParameters::modified(const BlenderViewportParameters &other) const
{
PassType display_pass = PASS_NONE;
if (b_v3d) {
BL::View3DShading b_view3dshading = b_v3d.shading();
PointerRNA cshading = RNA_pointer_get(&b_view3dshading.ptr, "cycles");
display_pass = (PassType)get_enum(cshading, "render_pass", -1, -1);
}
return display_pass;
return shader_modified(other) || film_modified(other);
}
bool BlenderViewportParameters::use_custom_shader() const
{
return !(use_scene_world && use_scene_lights);
}
PassType update_viewport_display_passes(BL::SpaceView3D &b_v3d, vector<Pass> &passes)
{
if (b_v3d) {
PassType display_pass = BlenderViewportParameters::get_viewport_display_render_pass(b_v3d);
const BlenderViewportParameters viewport_parameters(b_v3d);
const PassType display_pass = viewport_parameters.display_pass;
passes.clear();
Pass::add(display_pass, passes);

View File

@@ -18,17 +18,18 @@
#define __BLENDER_VIEWPORT_H__
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_blender_cpp.h"
#include "RNA_types.h"
#include "render/film.h"
#include "util/util_param.h"
CCL_NAMESPACE_BEGIN
class BlenderViewportParameters {
private:
public:
/* Shader. */
bool use_scene_world;
bool use_scene_lights;
float studiolight_rotate_z;
@@ -36,17 +37,24 @@ class BlenderViewportParameters {
float studiolight_background_alpha;
ustring studiolight_path;
/* Film. */
PassType display_pass;
BlenderViewportParameters();
BlenderViewportParameters(BL::SpaceView3D &b_v3d);
explicit BlenderViewportParameters(BL::SpaceView3D &b_v3d);
const bool modified(const BlenderViewportParameters &other) const;
const bool custom_viewport_parameters() const;
friend class BlenderSync;
/* Check whether any of shading related settings are different from the given parameters. */
bool shader_modified(const BlenderViewportParameters &other) const;
public:
/* Retrieve the render pass that needs to be displayed on the given `SpaceView3D`
* When the `b_v3d` parameter is not given `PASS_NONE` will be returned. */
static PassType get_viewport_display_render_pass(BL::SpaceView3D &b_v3d);
/* Check whether any of film related settings are different from the given parameters. */
bool film_modified(const BlenderViewportParameters &other) const;
/* Check whether any of settings are different from the given parameters. */
bool modified(const BlenderViewportParameters &other) const;
/* Returns truth when a custom shader defined by the viewport is to be used instead of the
* regular background shader or scene light. */
bool use_custom_shader() const;
};
PassType update_viewport_display_passes(BL::SpaceView3D &b_v3d, vector<Pass> &passes);

View File

@@ -726,7 +726,11 @@ class OptiXDevice : public CUDADevice {
}
}
else if (task.type == DeviceTask::SHADER) {
launch_shader_eval(task, thread_index);
// CUDA kernels are used when doing baking
if (optix_module == NULL)
CUDADevice::shader(task);
else
launch_shader_eval(task, thread_index);
}
else if (task.type == DeviceTask::DENOISE_BUFFER) {
// Set up a single tile that covers the whole task and denoise it

View File

@@ -195,31 +195,108 @@ ccl_device float2 regular_polygon_sample(float corners, float rotation, float u,
ccl_device float3 ensure_valid_reflection(float3 Ng, float3 I, float3 N)
{
float3 R;
float NI = dot(N, I);
float NgR, threshold;
float3 R = 2 * dot(N, I) * N - I;
/* Check if the incident ray is coming from behind normal N. */
if (NI > 0) {
/* Normal reflection */
R = (2 * NI) * N - I;
NgR = dot(Ng, R);
/* Reflection rays may always be at least as shallow as the incoming ray. */
float threshold = min(0.9f * dot(Ng, I), 0.01f);
if (dot(Ng, R) >= threshold) {
return N;
}
/* Reflection rays may always be at least as shallow as the incoming ray. */
threshold = min(0.9f * dot(Ng, I), 0.01f);
if (NgR >= threshold) {
return N;
/* Form coordinate system with Ng as the Z axis and N inside the X-Z-plane.
* The X axis is found by normalizing the component of N that's orthogonal to Ng.
* The Y axis isn't actually needed.
*/
float NdotNg = dot(N, Ng);
float3 X = normalize(N - NdotNg * Ng);
/* Keep math expressions. */
/* clang-format off */
/* Calculate N.z and N.x in the local coordinate system.
*
* The goal of this computation is to find a N' that is rotated towards Ng just enough
* to lift R' above the threshold (here called t), therefore dot(R', Ng) = t.
*
* According to the standard reflection equation,
* this means that we want dot(2*dot(N', I)*N' - I, Ng) = t.
*
* Since the Z axis of our local coordinate system is Ng, dot(x, Ng) is just x.z, so we get
* 2*dot(N', I)*N'.z - I.z = t.
*
* The rotation is simple to express in the coordinate system we formed -
* since N lies in the X-Z-plane, we know that N' will also lie in the X-Z-plane,
* so N'.y = 0 and therefore dot(N', I) = N'.x*I.x + N'.z*I.z .
*
* Furthermore, we want N' to be normalized, so N'.x = sqrt(1 - N'.z^2).
*
* With these simplifications,
* we get the final equation 2*(sqrt(1 - N'.z^2)*I.x + N'.z*I.z)*N'.z - I.z = t.
*
* The only unknown here is N'.z, so we can solve for that.
*
* The equation has four solutions in general:
*
* N'.z = +-sqrt(0.5*(+-sqrt(I.x^2*(I.x^2 + I.z^2 - t^2)) + t*I.z + I.x^2 + I.z^2)/(I.x^2 + I.z^2))
* We can simplify this expression a bit by grouping terms:
*
* a = I.x^2 + I.z^2
* b = sqrt(I.x^2 * (a - t^2))
* c = I.z*t + a
* N'.z = +-sqrt(0.5*(+-b + c)/a)
*
* Two solutions can immediately be discarded because they're negative so N' would lie in the
* lower hemisphere.
*/
/* clang-format on */
float Ix = dot(I, X), Iz = dot(I, Ng);
float Ix2 = sqr(Ix), Iz2 = sqr(Iz);
float a = Ix2 + Iz2;
float b = safe_sqrtf(Ix2 * (a - sqr(threshold)));
float c = Iz * threshold + a;
/* Evaluate both solutions.
* In many cases one can be immediately discarded (if N'.z would be imaginary or larger than
* one), so check for that first. If no option is viable (might happen in extreme cases like N
* being in the wrong hemisphere), give up and return Ng. */
float fac = 0.5f / a;
float N1_z2 = fac * (b + c), N2_z2 = fac * (-b + c);
bool valid1 = (N1_z2 > 1e-5f) && (N1_z2 <= (1.0f + 1e-5f));
bool valid2 = (N2_z2 > 1e-5f) && (N2_z2 <= (1.0f + 1e-5f));
float2 N_new;
if (valid1 && valid2) {
/* If both are possible, do the expensive reflection-based check. */
float2 N1 = make_float2(safe_sqrtf(1.0f - N1_z2), safe_sqrtf(N1_z2));
float2 N2 = make_float2(safe_sqrtf(1.0f - N2_z2), safe_sqrtf(N2_z2));
float R1 = 2 * (N1.x * Ix + N1.y * Iz) * N1.y - Iz;
float R2 = 2 * (N2.x * Ix + N2.y * Iz) * N2.y - Iz;
valid1 = (R1 >= 1e-5f);
valid2 = (R2 >= 1e-5f);
if (valid1 && valid2) {
/* If both solutions are valid, return the one with the shallower reflection since it will be
* closer to the input (if the original reflection wasn't shallow, we would not be in this
* part of the function). */
N_new = (R1 < R2) ? N1 : N2;
}
else {
/* If only one reflection is valid (= positive), pick that one. */
N_new = (R1 > R2) ? N1 : N2;
}
}
else if (valid1 || valid2) {
/* Only one solution passes the N'.z criterium, so pick that one. */
float Nz2 = valid1 ? N1_z2 : N2_z2;
N_new = make_float2(safe_sqrtf(1.0f - Nz2), safe_sqrtf(Nz2));
}
else {
/* Bad incident */
R = -I;
NgR = dot(Ng, R);
threshold = 0.01f;
return Ng;
}
R = R + Ng * (threshold - NgR); /* Lift the reflection above the threshold. */
return normalize(I * len(R) + R * len(I)); /* Find a bisector. */
return N_new.x * X + N_new.y * Ng;
}
CCL_NAMESPACE_END

View File

@@ -84,30 +84,67 @@ closure color principled_hair(normal N,
closure color henyey_greenstein(float g) BUILTIN;
closure color absorption() BUILTIN;
normal ensure_valid_reflection(normal Ng, normal I, normal N)
normal ensure_valid_reflection(normal Ng, vector I, normal N)
{
/* The implementation here mirrors the one in kernel_montecarlo.h,
* check there for an explanation of the algorithm. */
vector R;
float NI = dot(N, I);
float NgR, threshold;
if (NI > 0) {
R = (2 * NI) * N - I;
NgR = dot(Ng, R);
threshold = min(0.9 * dot(Ng, I), 0.01);
if (NgR >= threshold) {
return N;
float sqr(float x)
{
return x * x;
}
vector R = 2 * dot(N, I) * N - I;
float threshold = min(0.9 * dot(Ng, I), 0.01);
if (dot(Ng, R) >= threshold) {
return N;
}
float NdotNg = dot(N, Ng);
vector X = normalize(N - NdotNg * Ng);
float Ix = dot(I, X), Iz = dot(I, Ng);
float Ix2 = sqr(Ix), Iz2 = sqr(Iz);
float a = Ix2 + Iz2;
float b = sqrt(Ix2 * (a - sqr(threshold)));
float c = Iz * threshold + a;
float fac = 0.5 / a;
float N1_z2 = fac * (b + c), N2_z2 = fac * (-b + c);
int valid1 = (N1_z2 > 1e-5) && (N1_z2 <= (1.0 + 1e-5));
int valid2 = (N2_z2 > 1e-5) && (N2_z2 <= (1.0 + 1e-5));
float N_new_x, N_new_z;
if (valid1 && valid2) {
float N1_x = sqrt(1.0 - N1_z2), N1_z = sqrt(N1_z2);
float N2_x = sqrt(1.0 - N2_z2), N2_z = sqrt(N2_z2);
float R1 = 2 * (N1_x * Ix + N1_z * Iz) * N1_z - Iz;
float R2 = 2 * (N2_x * Ix + N2_z * Iz) * N2_z - Iz;
valid1 = (R1 >= 1e-5);
valid2 = (R2 >= 1e-5);
if (valid1 && valid2) {
N_new_x = (R1 < R2) ? N1_x : N2_x;
N_new_z = (R1 < R2) ? N1_z : N2_z;
}
else {
N_new_x = (R1 > R2) ? N1_x : N2_x;
N_new_z = (R1 > R2) ? N1_z : N2_z;
}
}
else if (valid1 || valid2) {
float Nz2 = valid1 ? N1_z2 : N2_z2;
N_new_x = sqrt(1.0 - Nz2);
N_new_z = sqrt(Nz2);
}
else {
R = -I;
NgR = dot(Ng, R);
threshold = 0.01;
return Ng;
}
R = R + Ng * (threshold - NgR);
return normalize(I * length(R) + R * length(I));
return N_new_x * X + N_new_z * Ng;
}
#endif /* CCL_STDOSL_H */

View File

@@ -654,8 +654,7 @@ static void update_attributes(AttributeSet &attributes, CachedData &cached_data,
list<Attribute>::iterator it;
for (it = attributes.attributes.begin(); it != attributes.attributes.end();) {
if (cached_attributes.find(&(*it)) == cached_attributes.end()) {
attributes.attributes.erase(it++);
attributes.modified = true;
attributes.remove(it++);
continue;
}

View File

@@ -606,7 +606,8 @@ void read_geometry_data(AlembicProcedural *proc,
template<typename T> struct value_type_converter {
using cycles_type = float;
static constexpr TypeDesc type_desc = TypeFloat;
/* Use `TypeDesc::FLOAT` instead of `TypeFloat` to work around a compiler bug in gcc 11. */
static constexpr TypeDesc type_desc = TypeDesc::FLOAT;
static constexpr const char *type_name = "float (default)";
static cycles_type convert_value(T value)

View File

@@ -383,6 +383,23 @@ AttributeStandard Attribute::name_standard(const char *name)
return ATTR_STD_NONE;
}
AttrKernelDataType Attribute::kernel_type(const Attribute &attr)
{
if (attr.element == ATTR_ELEMENT_CORNER) {
return AttrKernelDataType::UCHAR4;
}
if (attr.type == TypeDesc::TypeFloat) {
return AttrKernelDataType::FLOAT;
}
if (attr.type == TypeFloat2) {
return AttrKernelDataType::FLOAT2;
}
return AttrKernelDataType::FLOAT3;
}
void Attribute::get_uv_tiles(Geometry *geom,
AttributePrimitive prim,
unordered_set<int> &tiles) const
@@ -417,7 +434,7 @@ void Attribute::get_uv_tiles(Geometry *geom,
/* Attribute Set */
AttributeSet::AttributeSet(Geometry *geometry, AttributePrimitive prim)
: geometry(geometry), prim(prim)
: modified_flag(~0u), geometry(geometry), prim(prim)
{
}
@@ -440,7 +457,7 @@ Attribute *AttributeSet::add(ustring name, TypeDesc type, AttributeElement eleme
Attribute new_attr(name, type, element, geometry, prim);
attributes.emplace_back(std::move(new_attr));
modified = true;
tag_modified(attributes.back());
return &attributes.back();
}
@@ -462,8 +479,7 @@ void AttributeSet::remove(ustring name)
for (it = attributes.begin(); it != attributes.end(); it++) {
if (&*it == attr) {
modified = true;
attributes.erase(it);
remove(it);
return;
}
}
@@ -608,8 +624,7 @@ void AttributeSet::remove(AttributeStandard std)
for (it = attributes.begin(); it != attributes.end(); it++) {
if (&*it == attr) {
modified = true;
attributes.erase(it);
remove(it);
return;
}
}
@@ -634,6 +649,12 @@ void AttributeSet::remove(Attribute *attribute)
}
}
void AttributeSet::remove(list<Attribute>::iterator it)
{
tag_modified(*it);
attributes.erase(it);
}
void AttributeSet::resize(bool reserve_only)
{
foreach (Attribute &attr, attributes) {
@@ -674,15 +695,13 @@ void AttributeSet::update(AttributeSet &&new_attributes)
for (it = attributes.begin(); it != attributes.end();) {
if (it->std != ATTR_STD_NONE) {
if (new_attributes.find(it->std) == nullptr) {
modified = true;
attributes.erase(it++);
remove(it++);
continue;
}
}
else if (it->name != "") {
if (new_attributes.find(it->name) == nullptr) {
modified = true;
attributes.erase(it++);
remove(it++);
continue;
}
}
@@ -699,7 +718,27 @@ void AttributeSet::clear_modified()
foreach (Attribute &attr, attributes) {
attr.modified = false;
}
modified = false;
modified_flag = 0;
}
void AttributeSet::tag_modified(const Attribute &attr)
{
/* Some attributes are not stored in the various kernel attribute arrays
* (DeviceScene::attribute_*), so the modified flags are only set if the associated standard
* corresponds to an attribute which will be stored in the kernel's attribute arrays. */
const bool modifies_device_array = (attr.std != ATTR_STD_FACE_NORMAL &&
attr.std != ATTR_STD_VERTEX_NORMAL);
if (modifies_device_array) {
AttrKernelDataType kernel_type = Attribute::kernel_type(attr);
modified_flag |= (1u << kernel_type);
}
}
bool AttributeSet::modified(AttrKernelDataType kernel_type) const
{
return (modified_flag & (1u << kernel_type)) != 0;
}
/* AttributeRequest */

View File

@@ -39,6 +39,21 @@ class Hair;
class Mesh;
struct Transform;
/* AttrKernelDataType.
*
* The data type of the device arrays storing the attribute's data. Those data types are different
* than the ones for attributes as some attribute types are stored in the same array, e.g. Point,
* Vector, and Transform are all stored as float3 in the kernel.
*
* The values of this enumeration are also used as flags to detect changes in AttributeSet. */
enum AttrKernelDataType {
FLOAT = 0,
FLOAT2 = 1,
FLOAT3 = 2,
UCHAR4 = 3,
};
/* Attribute
*
* Arbitrary data layers on meshes.
@@ -167,6 +182,8 @@ class Attribute {
static const char *standard_name(AttributeStandard std);
static AttributeStandard name_standard(const char *name);
static AttrKernelDataType kernel_type(const Attribute &attr);
void get_uv_tiles(Geometry *geom, AttributePrimitive prim, unordered_set<int> &tiles) const;
};
@@ -175,11 +192,12 @@ class Attribute {
* Set of attributes on a mesh. */
class AttributeSet {
uint32_t modified_flag;
public:
Geometry *geometry;
AttributePrimitive prim;
list<Attribute> attributes;
bool modified = true;
AttributeSet(Geometry *geometry, AttributePrimitive prim);
AttributeSet(AttributeSet &&) = default;
@@ -197,6 +215,8 @@ class AttributeSet {
void remove(Attribute *attribute);
void remove(list<Attribute>::iterator it);
void resize(bool reserve_only = false);
void clear(bool preserve_voxel_data = false);
@@ -204,7 +224,18 @@ class AttributeSet {
* and remove any attribute not found on the new set from this. */
void update(AttributeSet &&new_attributes);
/* Return whether the attributes of the given kernel_type are modified, where "modified" means
* that some attributes of the given type were added or removed from this AttributeSet. This does
* not mean that the data of the remaining attributes in this AttributeSet were also modified. To
* check this, use Attribute.modified. */
bool modified(AttrKernelDataType kernel_type) const;
void clear_modified();
private:
/* Set the relevant modified flag for the attribute. Only attributes that are stored in device
* arrays will be considered for tagging this AttributeSet as modified. */
void tag_modified(const Attribute &attr);
};
/* AttributeRequest

View File

@@ -830,10 +830,13 @@ void GeometryManager::device_update_attributes(Device *device,
dscene->attributes_float3.alloc(attr_float3_size);
dscene->attributes_uchar4.alloc(attr_uchar4_size);
const bool copy_all_data = dscene->attributes_float.need_realloc() ||
dscene->attributes_float2.need_realloc() ||
dscene->attributes_float3.need_realloc() ||
dscene->attributes_uchar4.need_realloc();
/* The order of those flags needs to match that of AttrKernelDataType. */
const bool attributes_need_realloc[4] = {
dscene->attributes_float.need_realloc(),
dscene->attributes_float2.need_realloc(),
dscene->attributes_float3.need_realloc(),
dscene->attributes_uchar4.need_realloc(),
};
size_t attr_float_offset = 0;
size_t attr_float2_offset = 0;
@@ -852,7 +855,7 @@ void GeometryManager::device_update_attributes(Device *device,
if (attr) {
/* force a copy if we need to reallocate all the data */
attr->modified |= copy_all_data;
attr->modified |= attributes_need_realloc[Attribute::kernel_type(*attr)];
}
update_attribute_element_offset(geom,
@@ -875,7 +878,7 @@ void GeometryManager::device_update_attributes(Device *device,
if (subd_attr) {
/* force a copy if we need to reallocate all the data */
subd_attr->modified |= copy_all_data;
subd_attr->modified |= attributes_need_realloc[Attribute::kernel_type(*subd_attr)];
}
update_attribute_element_offset(mesh,
@@ -906,6 +909,10 @@ void GeometryManager::device_update_attributes(Device *device,
foreach (AttributeRequest &req, attributes.requests) {
Attribute *attr = values.find(req);
if (attr) {
attr->modified |= attributes_need_realloc[Attribute::kernel_type(*attr)];
}
update_attribute_element_offset(object->geometry,
dscene->attributes_float,
attr_float_offset,
@@ -941,10 +948,10 @@ void GeometryManager::device_update_attributes(Device *device,
/* copy to device */
progress.set_status("Updating Mesh", "Copying Attributes to device");
dscene->attributes_float.copy_to_device();
dscene->attributes_float2.copy_to_device();
dscene->attributes_float3.copy_to_device();
dscene->attributes_uchar4.copy_to_device();
dscene->attributes_float.copy_to_device_if_modified();
dscene->attributes_float2.copy_to_device_if_modified();
dscene->attributes_float3.copy_to_device_if_modified();
dscene->attributes_uchar4.copy_to_device_if_modified();
if (progress.get_cancel())
return;
@@ -1431,24 +1438,46 @@ static void update_device_flags_attribute(uint32_t &device_update_flags,
continue;
}
if (attr.element == ATTR_ELEMENT_CORNER) {
device_update_flags |= ATTR_UCHAR4_MODIFIED;
}
else if (attr.type == TypeDesc::TypeFloat) {
device_update_flags |= ATTR_FLOAT_MODIFIED;
}
else if (attr.type == TypeFloat2) {
device_update_flags |= ATTR_FLOAT2_MODIFIED;
}
else if (attr.type == TypeDesc::TypeMatrix) {
device_update_flags |= ATTR_FLOAT3_MODIFIED;
}
else if (attr.element != ATTR_ELEMENT_VOXEL) {
device_update_flags |= ATTR_FLOAT3_MODIFIED;
AttrKernelDataType kernel_type = Attribute::kernel_type(attr);
switch (kernel_type) {
case AttrKernelDataType::FLOAT: {
device_update_flags |= ATTR_FLOAT_MODIFIED;
break;
}
case AttrKernelDataType::FLOAT2: {
device_update_flags |= ATTR_FLOAT2_MODIFIED;
break;
}
case AttrKernelDataType::FLOAT3: {
device_update_flags |= ATTR_FLOAT3_MODIFIED;
break;
}
case AttrKernelDataType::UCHAR4: {
device_update_flags |= ATTR_UCHAR4_MODIFIED;
break;
}
}
}
}
static void update_attribute_realloc_flags(uint32_t &device_update_flags,
const AttributeSet &attributes)
{
if (attributes.modified(AttrKernelDataType::FLOAT)) {
device_update_flags |= ATTR_FLOAT_NEEDS_REALLOC;
}
if (attributes.modified(AttrKernelDataType::FLOAT2)) {
device_update_flags |= ATTR_FLOAT2_NEEDS_REALLOC;
}
if (attributes.modified(AttrKernelDataType::FLOAT3)) {
device_update_flags |= ATTR_FLOAT3_NEEDS_REALLOC;
}
if (attributes.modified(AttrKernelDataType::UCHAR4)) {
device_update_flags |= ATTR_UCHAR4_NEEDS_REALLOC;
}
}
void GeometryManager::device_update_preprocess(Device *device, Scene *scene, Progress &progress)
{
if (!need_update() && !need_flags_update) {
@@ -1471,16 +1500,11 @@ void GeometryManager::device_update_preprocess(Device *device, Scene *scene, Pro
foreach (Geometry *geom, scene->geometry) {
geom->has_volume = false;
if (geom->attributes.modified) {
device_update_flags |= ATTRS_NEED_REALLOC;
}
update_attribute_realloc_flags(device_update_flags, geom->attributes);
if (geom->is_mesh()) {
Mesh *mesh = static_cast<Mesh *>(geom);
if (mesh->subd_attributes.modified) {
device_update_flags |= ATTRS_NEED_REALLOC;
}
update_attribute_realloc_flags(device_update_flags, mesh->subd_attributes);
}
foreach (Node *node, geom->get_used_shaders()) {
@@ -2039,7 +2063,7 @@ void GeometryManager::device_update(Device *device,
* for meshes with correct bounding boxes.
*
* This wouldn't cause wrong results, just true
* displacement might be less optimal ot calculate.
* displacement might be less optimal to calculate.
*/
scene->object_manager->need_flags_update = old_need_object_flags_update;
}

View File

@@ -32,7 +32,7 @@ typedef function<void(void)> TaskRunFunction;
/* Task Pool
*
* Pool of tasks that will be executed by the central TaskScheduler.For each
* Pool of tasks that will be executed by the central TaskScheduler. For each
* pool, we can wait for all tasks to be done, or cancel them before they are
* done.
*
@@ -77,7 +77,7 @@ class TaskPool {
/* Task Scheduler
*
* Central scheduler that holds running threads ready to execute tasks. A singe
* Central scheduler that holds running threads ready to execute tasks. A single
* queue holds the task from all pools. */
class TaskScheduler {

View File

@@ -43,6 +43,55 @@
# define FFMPEG_INLINE static inline
#endif
#if (LIBAVFORMAT_VERSION_MAJOR < 58) || \
((LIBAVFORMAT_VERSION_MAJOR == 58) && (LIBAVFORMAT_VERSION_MINOR < 76))
# define FFMPEG_USE_DURATION_WORKAROUND 1
/* Before ffmpeg 4.4, package duration calculation used depricated variables to calculate the
* packet duration. Use the function from commit
* github.com/FFmpeg/FFmpeg/commit/1c0885334dda9ee8652e60c586fa2e3674056586
* to calculate the correct framerate for ffmpeg < 4.4.
*/
FFMPEG_INLINE
void my_guess_pkt_duration(AVFormatContext *s, AVStream *st, AVPacket *pkt)
{
if (pkt->duration < 0 && st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
av_log(s,
AV_LOG_WARNING,
"Packet with invalid duration %" PRId64 " in stream %d\n",
pkt->duration,
pkt->stream_index);
pkt->duration = 0;
}
if (pkt->duration) {
return;
}
switch (st->codecpar->codec_type) {
case AVMEDIA_TYPE_VIDEO:
if (st->avg_frame_rate.num > 0 && st->avg_frame_rate.den > 0) {
pkt->duration = av_rescale_q(1, av_inv_q(st->avg_frame_rate), st->time_base);
}
else if (st->time_base.num * 1000LL > st->time_base.den) {
pkt->duration = 1;
}
break;
case AVMEDIA_TYPE_AUDIO: {
int frame_size = av_get_audio_frame_duration2(st->codecpar, pkt->size);
if (frame_size && st->codecpar->sample_rate) {
pkt->duration = av_rescale_q(
frame_size, (AVRational){1, st->codecpar->sample_rate}, st->time_base);
}
break;
}
default:
break;
}
}
#endif
FFMPEG_INLINE
void my_update_cur_dts(AVFormatContext *s, AVStream *ref_st, int64_t timestamp)
{

View File

@@ -32,6 +32,7 @@
#include <commctrl.h>
#include <psapi.h>
#include <shellapi.h>
#include <shellscalingapi.h>
#include <shlobj.h>
#include <tlhelp32.h>
#include <windowsx.h>
@@ -97,41 +98,6 @@
# define VK_GR_LESS 0xE2
#endif // VK_GR_LESS
#ifndef VK_MEDIA_NEXT_TRACK
# define VK_MEDIA_NEXT_TRACK 0xB0
#endif // VK_MEDIA_NEXT_TRACK
#ifndef VK_MEDIA_PREV_TRACK
# define VK_MEDIA_PREV_TRACK 0xB1
#endif // VK_MEDIA_PREV_TRACK
#ifndef VK_MEDIA_STOP
# define VK_MEDIA_STOP 0xB2
#endif // VK_MEDIA_STOP
#ifndef VK_MEDIA_PLAY_PAUSE
# define VK_MEDIA_PLAY_PAUSE 0xB3
#endif // VK_MEDIA_PLAY_PAUSE
// Window message newer than Windows 7
#ifndef WM_DPICHANGED
# define WM_DPICHANGED 0x02E0
#endif // WM_DPICHANGED
// WM_POINTER API messages minimum Windows 7
#ifndef WM_POINTERENTER
# define WM_POINTERENTER 0x0249
#endif // WM_POINTERENTER
#ifndef WM_POINTERDOWN
# define WM_POINTERDOWN 0x0246
#endif // WM_POINTERDOWN
#ifndef WM_POINTERUPDATE
# define WM_POINTERUPDATE 0x0245
#endif // WM_POINTERUPDATE
#ifndef WM_POINTERUP
# define WM_POINTERUP 0x0247
#endif // WM_POINTERUP
#ifndef WM_POINTERLEAVE
# define WM_POINTERLEAVE 0x024A
#endif // WM_POINTERLEAVE
/* Workaround for some laptop touchpads, some of which seems to
* have driver issues which makes it so window function receives
* the message, but PeekMessage doesn't pick those messages for
@@ -173,24 +139,6 @@ static void initRawInput()
#undef DEVICE_COUNT
}
#ifndef DPI_ENUMS_DECLARED
typedef enum PROCESS_DPI_AWARENESS {
PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS;
typedef enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
# define USER_DEFAULT_SCREEN_DPI 96
# define DPI_ENUMS_DECLARED
#endif
typedef HRESULT(API *GHOST_WIN32_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS);
typedef BOOL(API *GHOST_WIN32_EnableNonClientDpiScaling)(HWND);
@@ -205,15 +153,7 @@ GHOST_SystemWin32::GHOST_SystemWin32()
// Tell Windows we are per monitor DPI aware. This disables the default
// blurry scaling and enables WM_DPICHANGED to allow us to draw at proper DPI.
HMODULE m_shcore = ::LoadLibrary("Shcore.dll");
if (m_shcore) {
GHOST_WIN32_SetProcessDpiAwareness fpSetProcessDpiAwareness =
(GHOST_WIN32_SetProcessDpiAwareness)::GetProcAddress(m_shcore, "SetProcessDpiAwareness");
if (fpSetProcessDpiAwareness) {
fpSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
}
}
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
// Check if current keyboard layout uses AltGr and save keylayout ID for
// specialized handling if keys like VK_OEM_*. I.e. french keylayout
@@ -581,14 +521,7 @@ GHOST_TSuccess GHOST_SystemWin32::init()
InitCommonControls();
/* Disable scaling on high DPI displays on Vista */
HMODULE
user32 = ::LoadLibraryA("user32.dll");
typedef BOOL(WINAPI * LPFNSETPROCESSDPIAWARE)();
LPFNSETPROCESSDPIAWARE SetProcessDPIAware = (LPFNSETPROCESSDPIAWARE)GetProcAddress(
user32, "SetProcessDPIAware");
if (SetProcessDPIAware)
SetProcessDPIAware();
FreeLibrary(user32);
SetProcessDPIAware();
initRawInput();
m_lfstart = ::GetTickCount();

View File

@@ -84,9 +84,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_wantAlphaBackground(alphaBackground),
m_normal_state(GHOST_kWindowStateNormal),
m_user32(NULL),
m_fpGetPointerInfoHistory(NULL),
m_fpGetPointerPenInfoHistory(NULL),
m_fpGetPointerTouchInfoHistory(NULL),
m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : HWND_DESKTOP),
m_debug_context(is_debug)
{
@@ -153,19 +150,7 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_user32 = ::LoadLibrary("user32.dll");
if (m_hWnd) {
if (m_user32) {
// Touch enabled screens with pen support by default have gestures
// enabled, which results in a delay between the pointer down event
// and the first move when using the stylus. RegisterTouchWindow
// disables the new gesture architecture enabling the events to be
// sent immediately to the application rather than being absorbed by
// the gesture API.
GHOST_WIN32_RegisterTouchWindow pRegisterTouchWindow = (GHOST_WIN32_RegisterTouchWindow)
GetProcAddress(m_user32, "RegisterTouchWindow");
if (pRegisterTouchWindow) {
pRegisterTouchWindow(m_hWnd, 0);
}
}
RegisterTouchWindow(m_hWnd, 0);
// Register this window as a droptarget. Requires m_hWnd to be valid.
// Note that OleInitialize(0) has to be called prior to this. Done in GHOST_SystemWin32.
@@ -232,16 +217,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
}
}
// Initialize Windows Ink
if (m_user32) {
m_fpGetPointerInfoHistory = (GHOST_WIN32_GetPointerInfoHistory)::GetProcAddress(
m_user32, "GetPointerInfoHistory");
m_fpGetPointerPenInfoHistory = (GHOST_WIN32_GetPointerPenInfoHistory)::GetProcAddress(
m_user32, "GetPointerPenInfoHistory");
m_fpGetPointerTouchInfoHistory = (GHOST_WIN32_GetPointerTouchInfoHistory)::GetProcAddress(
m_user32, "GetPointerTouchInfoHistory");
}
// Initialize Wintab
m_wintab.handle = ::LoadLibrary("Wintab32.dll");
if (m_wintab.handle && m_system->getTabletAPI() != GHOST_kTabletNative) {
@@ -326,9 +301,6 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
if (m_user32) {
FreeLibrary(m_user32);
m_user32 = NULL;
m_fpGetPointerInfoHistory = NULL;
m_fpGetPointerPenInfoHistory = NULL;
m_fpGetPointerTouchInfoHistory = NULL;
}
if (m_customCursor) {
@@ -950,15 +922,14 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem();
GHOST_TUns32 outCount;
if (!(m_fpGetPointerInfoHistory && m_fpGetPointerInfoHistory(pointerId, &outCount, NULL))) {
if (!(GetPointerInfoHistory(pointerId, &outCount, NULL))) {
return GHOST_kFailure;
}
auto pointerPenInfo = std::vector<POINTER_PEN_INFO>(outCount);
outPointerInfo.resize(outCount);
if (!(m_fpGetPointerPenInfoHistory &&
m_fpGetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) {
if (!(GetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) {
return GHOST_kFailure;
}

View File

@@ -53,177 +53,12 @@ typedef BOOL(API *GHOST_WIN32_WTPacket)(HCTX, UINT, LPVOID);
typedef BOOL(API *GHOST_WIN32_WTEnable)(HCTX, BOOL);
typedef BOOL(API *GHOST_WIN32_WTOverlap)(HCTX, BOOL);
// typedef to user32 functions to disable gestures on windows
typedef BOOL(API *GHOST_WIN32_RegisterTouchWindow)(HWND hwnd, ULONG ulFlags);
// typedefs for user32 functions to allow dynamic loading of Windows 10 DPI scaling functions
typedef UINT(API *GHOST_WIN32_GetDpiForWindow)(HWND);
#ifndef USER_DEFAULT_SCREEN_DPI
# define USER_DEFAULT_SCREEN_DPI 96
#endif // USER_DEFAULT_SCREEN_DPI
// typedefs for user32 functions to allow pointer functions
enum tagPOINTER_INPUT_TYPE {
PT_POINTER = 1, // Generic pointer
PT_TOUCH = 2, // Touch
PT_PEN = 3, // Pen
PT_MOUSE = 4, // Mouse
#if (WINVER >= 0x0603)
PT_TOUCHPAD = 5, // Touchpad
#endif /* WINVER >= 0x0603 */
};
typedef enum tagPOINTER_BUTTON_CHANGE_TYPE {
POINTER_CHANGE_NONE,
POINTER_CHANGE_FIRSTBUTTON_DOWN,
POINTER_CHANGE_FIRSTBUTTON_UP,
POINTER_CHANGE_SECONDBUTTON_DOWN,
POINTER_CHANGE_SECONDBUTTON_UP,
POINTER_CHANGE_THIRDBUTTON_DOWN,
POINTER_CHANGE_THIRDBUTTON_UP,
POINTER_CHANGE_FOURTHBUTTON_DOWN,
POINTER_CHANGE_FOURTHBUTTON_UP,
POINTER_CHANGE_FIFTHBUTTON_DOWN,
POINTER_CHANGE_FIFTHBUTTON_UP,
} POINTER_BUTTON_CHANGE_TYPE;
typedef DWORD POINTER_INPUT_TYPE;
typedef UINT32 POINTER_FLAGS;
#define POINTER_FLAG_NONE 0x00000000
#define POINTER_FLAG_NEW 0x00000001
#define POINTER_FLAG_INRANGE 0x00000002
#define POINTER_FLAG_INCONTACT 0x00000004
#define POINTER_FLAG_FIRSTBUTTON 0x00000010
#define POINTER_FLAG_SECONDBUTTON 0x00000020
#define POINTER_FLAG_THIRDBUTTON 0x00000040
#define POINTER_FLAG_FOURTHBUTTON 0x00000080
#define POINTER_FLAG_FIFTHBUTTON 0x00000100
#define POINTER_FLAG_PRIMARY 0x00002000
#define POINTER_FLAG_CONFIDENCE 0x000004000
#define POINTER_FLAG_CANCELED 0x000008000
#define POINTER_FLAG_DOWN 0x00010000
#define POINTER_FLAG_UPDATE 0x00020000
#define POINTER_FLAG_UP 0x00040000
#define POINTER_FLAG_WHEEL 0x00080000
#define POINTER_FLAG_HWHEEL 0x00100000
#define POINTER_FLAG_CAPTURECHANGED 0x00200000
#define POINTER_FLAG_HASTRANSFORM 0x00400000
typedef struct tagPOINTER_INFO {
POINTER_INPUT_TYPE pointerType;
UINT32 pointerId;
UINT32 frameId;
POINTER_FLAGS pointerFlags;
HANDLE sourceDevice;
HWND hwndTarget;
POINT ptPixelLocation;
POINT ptHimetricLocation;
POINT ptPixelLocationRaw;
POINT ptHimetricLocationRaw;
DWORD dwTime;
UINT32 historyCount;
INT32 InputData;
DWORD dwKeyStates;
UINT64 PerformanceCount;
POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
} POINTER_INFO;
typedef UINT32 PEN_FLAGS;
#define PEN_FLAG_NONE 0x00000000 // Default
#define PEN_FLAG_BARREL 0x00000001 // The barrel button is pressed
#define PEN_FLAG_INVERTED 0x00000002 // The pen is inverted
#define PEN_FLAG_ERASER 0x00000004 // The eraser button is pressed
typedef UINT32 PEN_MASK;
#define PEN_MASK_NONE 0x00000000 // Default - none of the optional fields are valid
#define PEN_MASK_PRESSURE 0x00000001 // The pressure field is valid
#define PEN_MASK_ROTATION 0x00000002 // The rotation field is valid
#define PEN_MASK_TILT_X 0x00000004 // The tiltX field is valid
#define PEN_MASK_TILT_Y 0x00000008 // The tiltY field is valid
typedef struct tagPOINTER_PEN_INFO {
POINTER_INFO pointerInfo;
PEN_FLAGS penFlags;
PEN_MASK penMask;
UINT32 pressure;
UINT32 rotation;
INT32 tiltX;
INT32 tiltY;
} POINTER_PEN_INFO;
/*
* Flags that appear in pointer input message parameters
*/
#define POINTER_MESSAGE_FLAG_NEW 0x00000001 // New pointer
#define POINTER_MESSAGE_FLAG_INRANGE 0x00000002 // Pointer has not departed
#define POINTER_MESSAGE_FLAG_INCONTACT 0x00000004 // Pointer is in contact
#define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010 // Primary action
#define POINTER_MESSAGE_FLAG_SECONDBUTTON 0x00000020 // Secondary action
#define POINTER_MESSAGE_FLAG_THIRDBUTTON 0x00000040 // Third button
#define POINTER_MESSAGE_FLAG_FOURTHBUTTON 0x00000080 // Fourth button
#define POINTER_MESSAGE_FLAG_FIFTHBUTTON 0x00000100 // Fifth button
#define POINTER_MESSAGE_FLAG_PRIMARY 0x00002000 // Pointer is primary
#define POINTER_MESSAGE_FLAG_CONFIDENCE \
0x00004000 // Pointer is considered unlikely to be accidental
#define POINTER_MESSAGE_FLAG_CANCELED 0x00008000 // Pointer is departing in an abnormal manner
typedef UINT32 TOUCH_FLAGS;
#define TOUCH_FLAG_NONE 0x00000000 // Default
typedef UINT32 TOUCH_MASK;
#define TOUCH_MASK_NONE 0x00000000 // Default - none of the optional fields are valid
#define TOUCH_MASK_CONTACTAREA 0x00000001 // The rcContact field is valid
#define TOUCH_MASK_ORIENTATION 0x00000002 // The orientation field is valid
#define TOUCH_MASK_PRESSURE 0x00000004 // The pressure field is valid
typedef struct tagPOINTER_TOUCH_INFO {
POINTER_INFO pointerInfo;
TOUCH_FLAGS touchFlags;
TOUCH_MASK touchMask;
RECT rcContact;
RECT rcContactRaw;
UINT32 orientation;
UINT32 pressure;
} POINTER_TOUCH_INFO;
/*
* Macros to retrieve information from pointer input message parameters
*/
#define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam))
#define IS_POINTER_FLAG_SET_WPARAM(wParam, flag) (((DWORD)HIWORD(wParam) & (flag)) == (flag))
#define IS_POINTER_NEW_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_NEW)
#define IS_POINTER_INRANGE_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INRANGE)
#define IS_POINTER_INCONTACT_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INCONTACT)
#define IS_POINTER_FIRSTBUTTON_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON)
#define IS_POINTER_SECONDBUTTON_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON)
#define IS_POINTER_THIRDBUTTON_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON)
#define IS_POINTER_FOURTHBUTTON_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON)
#define IS_POINTER_FIFTHBUTTON_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON)
#define IS_POINTER_PRIMARY_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_PRIMARY)
#define HAS_POINTER_CONFIDENCE_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CONFIDENCE)
#define IS_POINTER_CANCELED_WPARAM(wParam) \
IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CANCELED)
typedef BOOL(WINAPI *GHOST_WIN32_GetPointerInfoHistory)(UINT32 pointerId,
UINT32 *entriesCount,
POINTER_INFO *pointerInfo);
typedef BOOL(WINAPI *GHOST_WIN32_GetPointerPenInfoHistory)(UINT32 pointerId,
UINT32 *entriesCount,
POINTER_PEN_INFO *penInfo);
typedef BOOL(WINAPI *GHOST_WIN32_GetPointerTouchInfoHistory)(UINT32 pointerId,
UINT32 *entriesCount,
POINTER_TOUCH_INFO *touchInfo);
struct GHOST_PointerInfoWin32 {
GHOST_TInt32 pointerId;
GHOST_TInt32 isPrimary;
@@ -576,9 +411,6 @@ class GHOST_WindowWin32 : public GHOST_Window {
/** `user32.dll` handle */
HMODULE m_user32;
GHOST_WIN32_GetPointerInfoHistory m_fpGetPointerInfoHistory;
GHOST_WIN32_GetPointerPenInfoHistory m_fpGetPointerPenInfoHistory;
GHOST_WIN32_GetPointerTouchInfoHistory m_fpGetPointerTouchInfoHistory;
HWND m_parentWindowHwnd;

5
release/darwin/README.md Normal file
View File

@@ -0,0 +1,5 @@
Buildbot Configuration
======================
Files used by Buildbot's `package-code-binaires` step for the darwin platform.

View File

@@ -1,55 +0,0 @@
macOS app bundling guide
========================
Install Code Signing Certificate
--------------------------------
* Go to https://developer.apple.com/account/resources/certificates/list
* Download the Developer ID Application certificate.
* Double click the file and add to key chain (default options).
* Delete the file from the Downloads folder.
* You will also need to install a .p12 public/private key file for the
certificate. This is only available for the owner of the Blender account,
or can be exported and copied from another system that already has code
signing set up.
Find the codesigning identity by running:
$ security find-identity -v -p codesigning
"Developer ID Application: Stichting Blender Foundation" is the identity needed.
The long code at the start of the line is used as <identity> below.
Setup Apple ID
--------------
* The Apple ID must have two step verification enabled.
* Create an app specific password for the code signing app (label can be anything):
https://support.apple.com/en-us/HT204397
* Add the app specific password to keychain:
$ security add-generic-password -a <apple-id> -w <app-specific-password> -s altool-password
When running the bundle script, there will be a popup. To avoid that either:
* Click Always Allow in the popup
* In the Keychain Access app, change the Access Control settings on altool-password
Bundle
------
Then the bundle is created as follows:
$ ./bundle.sh --source <sourcedir> --dmg <dmg> --bundle-id <bundleid> --username <apple-id> --password "@keychain:altool-password" --codesign <identity>
<sourcedir> directory where built Blender.app is
<dmg> location and name of the final disk image
<bundleid> id on notarization, for example org.blenderfoundation.blender.release
<apple-id> your appleid email
<identity> codesigning identity
When specifying only --sourcedir and --dmg, the build will not be signed.
Example :
$ ./bundle.sh --source /data/build/bin --dmg /data/Blender-2.8-alpha-macOS-10.11.dmg --bundle-id org.blenderfoundation.blender.release --username "foo@mac.com" --password "@keychain:altool-password" --codesign AE825E26F12D08B692F360133210AF46F4CF7B97

View File

@@ -1,18 +0,0 @@
tell application "Finder"
tell disk "Blender"
open
set current view of container window to icon view
set toolbar visible of container window to false
set statusbar visible of container window to false
set the bounds of container window to {100, 100, 640, 472}
set theViewOptions to icon view options of container window
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 128
set background picture of theViewOptions to file ".background:background.tif"
set position of item " " of container window to {400, 190}
set position of item "blender.app" of container window to {135, 190}
update without registering applications
delay 5
close
end tell
end tell

View File

@@ -1,212 +0,0 @@
#!/usr/bin/env bash
#
# Script to create a macOS dmg file for Blender builds, including code
# signing and notarization for releases.
# Check that we have all needed tools.
for i in osascript git codesign hdiutil xcrun ; do
if [ ! -x "$(which ${i})" ]; then
echo "Unable to execute command $i, macOS broken?"
exit 1
fi
done
# Defaults settings.
_script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
_volume_name="Blender"
_tmp_dir="$(mktemp -d)"
_tmp_dmg="/tmp/blender-tmp.dmg"
_background_image="${_script_dir}/background.tif"
_mount_dir="/Volumes/${_volume_name}"
_entitlements="${_script_dir}/entitlements.plist"
# Handle arguments.
while [[ $# -gt 0 ]]; do
key=$1
case $key in
-s|--source)
SRC_DIR="$2"
shift
shift
;;
-d|--dmg)
DEST_DMG="$2"
shift
shift
;;
-b|--bundle-id)
N_BUNDLE_ID="$2"
shift
shift
;;
-u|--username)
N_USERNAME="$2"
shift
shift
;;
-p|--password)
N_PASSWORD="$2"
shift
shift
;;
-c|--codesign)
C_CERT="$2"
shift
shift
;;
--background-image)
_background_image="$2"
shift
shift
;;
-h|--help)
echo "Usage:"
echo " $(basename "$0") --source DIR --dmg IMAGENAME "
echo " optional arguments:"
echo " --codesign <certname>"
echo " --username <username>"
echo " --password <password>"
echo " --bundle-id <bundleid>"
echo " Check https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution/customizing_the_notarization_workflow "
exit 1
;;
esac
done
if [ ! -d "${SRC_DIR}/Blender.app" ]; then
echo "use --source parameter to set source directory where Blender.app can be found"
exit 1
fi
if [ -z "${DEST_DMG}" ]; then
echo "use --dmg parameter to set output dmg name"
exit 1
fi
# Destroy destination dmg if there is any.
test -f "${DEST_DMG}" && rm "${DEST_DMG}"
if [ -d "${_mount_dir}" ]; then
echo -n "Ejecting existing blender volume.."
DEV_FILE=$(mount | grep "${_mount_dir}" | awk '{ print $1 }')
diskutil eject "${DEV_FILE}" || exit 1
echo
fi
# Copy dmg contents.
echo -n "Copying Blender.app..."
cp -r "${SRC_DIR}/Blender.app" "${_tmp_dir}/" || exit 1
echo
# Create the disk image.
_directory_size=$(du -sh ${_tmp_dir} | awk -F'[^0-9]*' '$0=$1')
_image_size=$(echo "${_directory_size}" + 400 | bc) # extra 400 need for codesign to work (why on earth?)
echo
echo -n "Creating disk image of size ${_image_size}M.."
test -f "${_tmp_dmg}" && rm "${_tmp_dmg}"
hdiutil create -size "${_image_size}m" -fs HFS+ -srcfolder "${_tmp_dir}" -volname "${_volume_name}" -format UDRW "${_tmp_dmg}" -mode 755
echo "Mounting readwrite image..."
hdiutil attach -readwrite -noverify -noautoopen "${_tmp_dmg}"
echo "Setting background picture.."
if ! test -z "${_background_image}"; then
echo "Copying background image ..."
test -d "${_mount_dir}/.background" || mkdir "${_mount_dir}/.background"
_background_image_NAME=$(basename "${_background_image}")
cp "${_background_image}" "${_mount_dir}/.background/${_background_image_NAME}"
fi
echo "Creating link to /Applications ..."
ln -s /Applications "${_mount_dir}/Applications"
echo "Renaming Applications to empty string."
mv ${_mount_dir}/Applications "${_mount_dir}/ "
echo "Running applescript to set folder looks ..."
cat "${_script_dir}/blender.applescript" | osascript
echo "Waiting after applescript ..."
sleep 5
if [ ! -z "${C_CERT}" ]; then
# Codesigning requires all libs and binaries to be signed separately.
echo -n "Codesigning Python"
for f in $(find "${_mount_dir}/Blender.app/Contents/Resources" -name "python*"); do
if [ -x ${f} ] && [ ! -d ${f} ]; then
codesign --remove-signature "${f}"
codesign --timestamp --options runtime --entitlements="${_entitlements}" --sign "${C_CERT}" "${f}"
fi
done
echo ; echo -n "Codesigning .dylib and .so libraries"
for f in $(find "${_mount_dir}/Blender.app" -name "*.dylib" -o -name "*.so"); do
codesign --remove-signature "${f}"
codesign --timestamp --options runtime --entitlements="${_entitlements}" --sign "${C_CERT}" "${f}"
done
echo ; echo -n "Codesigning Blender.app"
codesign --remove-signature "${_mount_dir}/Blender.app"
codesign --timestamp --options runtime --entitlements="${_entitlements}" --sign "${C_CERT}" "${_mount_dir}/Blender.app"
echo
else
echo "No codesigning cert given, skipping..."
fi
# Need to eject dev files to remove /dev files and free .dmg for converting
echo "Unmounting rw disk image ..."
DEV_FILE=$(mount | grep "${_mount_dir}" | awk '{ print $1 }')
diskutil eject "${DEV_FILE}"
sleep 3
echo "Compressing disk image ..."
hdiutil convert "${_tmp_dmg}" -format UDZO -o "${DEST_DMG}"
# Codesign the dmg
if [ ! -z "${C_CERT}" ]; then
echo -n "Codesigning dmg..."
codesign --timestamp --force --sign "${C_CERT}" "${DEST_DMG}"
echo
fi
# Cleanup
rm -rf "${_tmp_dir}"
rm "${_tmp_dmg}"
# Notarize
if [ ! -z "${N_USERNAME}" ] && [ ! -z "${N_PASSWORD}" ] && [ ! -z "${N_BUNDLE_ID}" ]; then
# Send to Apple
echo "Sending ${DEST_DMG} for notarization..."
_tmpout=$(mktemp)
echo xcrun altool --notarize-app --verbose -f "${DEST_DMG}" --primary-bundle-id "${N_BUNDLE_ID}" --username "${N_USERNAME}" --password "${N_PASSWORD}"
xcrun altool --notarize-app --verbose -f "${DEST_DMG}" --primary-bundle-id "${N_BUNDLE_ID}" --username "${N_USERNAME}" --password "${N_PASSWORD}" >${_tmpout} 2>&1
# Parse request uuid
_requuid=$(cat "${_tmpout}" | grep "RequestUUID" | awk '{ print $3 }')
echo "RequestUUID: ${_requuid}"
if [ ! -z "${_requuid}" ]; then
# Wait for Apple to confirm notarization is complete
echo "Waiting for notarization to be complete.."
for c in {20..0};do
sleep 600
xcrun altool --notarization-info "${_requuid}" --username "${N_USERNAME}" --password "${N_PASSWORD}" >${_tmpout} 2>&1
_status=$(cat "${_tmpout}" | grep "Status:" | awk '{ print $2 }')
if [ "${_status}" == "invalid" ]; then
echo "Got invalid notarization!"
break;
fi
if [ "${_status}" == "success" ]; then
echo -n "Notarization successful! Stapling..."
xcrun stapler staple -v "${DEST_DMG}"
break;
fi
echo "Notarization in progress, waiting..."
done
else
cat ${_tmpout}
echo "Error getting RequestUUID, notarization unsuccessful"
fi
else
echo "No notarization credentials supplied, skipping..."
fi
echo "..done. You should have ${DEST_DMG} ready to upload"

View File

@@ -40,6 +40,25 @@
</screenshot>
</screenshots>
<releases>
<release version="2.93" date="2021-06-02">
<description>
<p>New features:</p>
<ul>
<li>Mesh primitive nodes</li>
<li>Line Art</li>
<li>EEVEE Realistic depth of field and volumetrics</li>
<li>Spreadsheet editor</li>
</ul>
<p>Enhancements:</p>
<ul>
<li>Geometry nodes 22 new nodes and imrpoved attribute search</li>
<li>Mask loops, textures and patterns for sculpting</li>
<li>Grease pencil interpolate refactored and SVG and PDF support</li>
<li>Persistent Data rendering settings for Cycles</li>
<li>Video Sequencer Editor auto-proxy system</li>
</ul>
</description>
</release>
<release version="2.92" date="2021-02-25">
<description>
<p>New features:</p>

View File

@@ -0,0 +1,17 @@
Snap Configuration
===================
Files used by Buildbot's `package-code-store-snap` and `deliver-code-store-snap` steps.
Build pipeline snap tracks and channels
```
<track>/stable
- Latest stable release for the specified track
<track>/candidate
- Test builds for the upcoming stable release - *not used for now*
<track>/beta
- Nightly automated builds provided by a release branch
<track>/egde/<branch>
- Nightly or on demand builds - will also make use of branch
```

View File

@@ -1,38 +0,0 @@
Snap Package Instructions
=========================
This folder contains the scripts for creating and uploading the snap on:
https://snapcraft.io/blender
Setup
-----
This has only been tested to work on Ubuntu.
# Install required packages
sudo apt install snapd snapcraft
Steps
-----
# Build the snap file
python3 bundle.py --version 2.XX --url https://download.blender.org/release/Blender2.XX/blender-2.XX-x86_64.tar.bz2
# Install snap to test
# --dangerous is needed since the snap has not been signed yet
# --classic is required for installing Blender in general
sudo snap install --dangerous --classic blender_2.XX_amd64.snap
# Upload
snapcraft push --release=stable blender_2.XX_amd64.snap
Release Values
--------------
stable: final release
candidate: release candidates

View File

@@ -10,12 +10,7 @@ description: |
scientists, students, VFX experts, animators, game artists, modders, and
the list goes on.
The standard snap channels are used in the following way:
stable - Latest stable release.
candidate - Test builds for the upcoming stable release.
icon: ../icons/scalable/apps/blender.svg
icon: @ICON_PATH@
passthrough:
license: GPL-3.0
@@ -27,13 +22,14 @@ apps:
command: ./blender-wrapper
desktop: ./blender.desktop
base: core18
version: '@VERSION@'
grade: @GRADE@
parts:
blender:
plugin: dump
source: @URL@
source: @PACKAGE_PATH@
build-attributes: [keep-execstack, no-patchelf]
override-build: |
snapcraftctl build
@@ -47,7 +43,7 @@ parts:
- libxrender1
- libxxf86vm1
wrapper:
plugin: copy
plugin: dump
source: .
files:
blender-wrapper: blender-wrapper
stage:
- ./blender-wrapper

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python3
import argparse
import os
import pathlib
import subprocess
parser = argparse.ArgumentParser()
parser.add_argument("--version", required=True)
parser.add_argument("--url", required=True)
parser.add_argument("--grade", default="stable", choices=["stable", "devel"])
args = parser.parse_args()
yaml_text = pathlib.Path("snapcraft.yaml.in").read_text()
yaml_text = yaml_text.replace("@VERSION@", args.version)
yaml_text = yaml_text.replace("@URL@", args.url)
yaml_text = yaml_text.replace("@GRADE@", args.grade)
pathlib.Path("snapcraft.yaml").write_text(yaml_text)
subprocess.call(["snapcraft", "clean"])
subprocess.call(["snapcraft", "snap"])

View File

@@ -58,6 +58,7 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.sndparticle_combined_export*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-combined-export"),
("bpy.types.fluiddomainsettings.use_collision_border_bottom*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-bottom"),
("bpy.types.fluiddomainsettings.vector_scale_with_magnitude*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-scale-with-magnitude"),
("bpy.types.spacespreadsheet.display_context_path_collapsed*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-display-context-path-collapsed"),
("bpy.types.fluiddomainsettings.use_collision_border_front*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-front"),
("bpy.types.fluiddomainsettings.use_collision_border_right*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-right"),
("bpy.types.cyclesobjectsettings.use_adaptive_subdivision*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-use-adaptive-subdivision"),
@@ -66,16 +67,20 @@ url_manual_mapping = (
("bpy.types.fluiddomainsettings.use_collision_border_left*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-left"),
("bpy.types.rendersettings_simplify_gpencil_view_modifier*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-modifier"),
("bpy.types.brushgpencilsettings.use_settings_stabilizer*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-stabilizer"),
("bpy.types.colormanagedsequencercolorspacesettings.name*", "render/color_management.html#bpy-types-colormanagedsequencercolorspacesettings-name"),
("bpy.types.fluiddomainsettings.use_collision_border_top*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-top"),
("bpy.types.gpencilsculptsettings.use_multiframe_falloff*", "grease_pencil/multiframe.html#bpy-types-gpencilsculptsettings-use-multiframe-falloff"),
("bpy.types.movietrackingsettings.use_keyframe_selection*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-keyframe-selection"),
("bpy.types.rendersettings.simplify_gpencil_antialiasing*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-antialiasing"),
("bpy.types.spaceoutliner.use_filter_lib_override_system*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-lib-override-system"),
("bpy.types.toolsettings.use_transform_pivot_point_align*", "scene_layout/object/tools/tool_settings.html#bpy-types-toolsettings-use-transform-pivot-point-align"),
("bpy.types.brush.show_multiplane_scrape_planes_preview*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-show-multiplane-scrape-planes-preview"),
("bpy.types.cyclesrendersettings.offscreen_dicing_scale*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-offscreen-dicing-scale"),
("bpy.types.fluiddomainsettings.sndparticle_bubble_drag*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-drag"),
("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/backbone_stretcher.html#bpy-types-linestylegeometrymodifier-backbonestretcher"),
("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/parameter_editor/line_style/modifiers/geometry/sinus_displacement.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"),
("bpy.types.colormanageddisplaysettings.display_device*", "render/color_management.html#bpy-types-colormanageddisplaysettings-display-device"),
("bpy.types.colormanagedviewsettings.use_curve_mapping*", "render/color_management.html#bpy-types-colormanagedviewsettings-use-curve-mapping"),
("bpy.types.fluiddomainsettings.color_ramp_field_scale*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field-scale"),
("bpy.types.fluiddomainsettings.use_adaptive_timesteps*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-adaptive-timesteps"),
("bpy.types.fluiddomainsettings.use_dissolve_smoke_log*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-dissolve-smoke-log"),
@@ -117,6 +122,8 @@ url_manual_mapping = (
("bpy.types.brush.use_cloth_pin_simulation_boundary*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-use-cloth-pin-simulation-boundary"),
("bpy.types.brushgpencilsettings.show_fill_boundary*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-show-fill-boundary"),
("bpy.types.brushgpencilsettings.use_default_eraser*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-use-default-eraser"),
("bpy.types.colormanagedsequencercolorspacesettings*", "render/color_management.html#bpy-types-colormanagedsequencercolorspacesettings"),
("bpy.types.colormanagedviewsettings.view_transform*", "render/color_management.html#bpy-types-colormanagedviewsettings-view-transform"),
("bpy.types.cyclesrendersettings.camera_cull_margin*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-camera-cull-margin"),
("bpy.types.fluiddomainsettings.export_manta_script*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-export-manta-script"),
("bpy.types.fluiddomainsettings.fractions_threshold*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-fractions-threshold"),
@@ -164,6 +171,7 @@ url_manual_mapping = (
("bpy.types.rigidbodyconstraint.breaking_threshold*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-breaking-threshold"),
("bpy.types.spacedopesheeteditor.show_pose_markers*", "animation/markers.html#bpy-types-spacedopesheeteditor-show-pose-markers"),
("bpy.types.spaceoutliner.use_filter_object_camera*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-camera"),
("bpy.types.spaceoutliner.use_filter_object_others*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-others"),
("bpy.types.toolsettings.proportional_edit_falloff*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-proportional-edit-falloff"),
("bpy.types.toolsettings.use_edge_path_live_unwrap*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-edge-path-live-unwrap"),
("bpy.types.toolsettings.use_gpencil_draw_additive*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-draw-additive"),
@@ -192,12 +200,14 @@ url_manual_mapping = (
("bpy.types.materialgpencilstyle.use_fill_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-holdout"),
("bpy.types.particlesettings.use_parent_particles*", "physics/particles/emitter/render.html#bpy-types-particlesettings-use-parent-particles"),
("bpy.types.rigidbodyconstraint.solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-solver-iterations"),
("bpy.types.spaceoutliner.use_filter_lib_override*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-lib-override"),
("bpy.types.spaceoutliner.use_filter_object_empty*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-empty"),
("bpy.types.spaceoutliner.use_filter_object_light*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-light"),
("bpy.types.spacesequenceeditor.proxy_render_size*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-proxy-render-size"),
("bpy.types.spacesequenceeditor.show_strip_offset*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-offset"),
("bpy.types.spacesequenceeditor.show_strip_source*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-strip-source"),
("bpy.types.toolsettings.gpencil_stroke_placement*", "grease_pencil/modes/draw/stroke_placement.html#bpy-types-toolsettings-gpencil-stroke-placement"),
("bpy.types.toolsettings.use_keyframe_cycle_aware*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-cycle-aware"),
("bpy.types.toolsettings.use_keyframe_insert_auto*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-insert-auto"),
("bpy.types.viewlayer.use_pass_cryptomatte_object*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-object"),
("bpy.ops.armature.rigify_apply_selection_colors*", "addons/rigging/rigify/metarigs.html#bpy-ops-armature-rigify-apply-selection-colors"),
@@ -291,6 +301,7 @@ url_manual_mapping = (
("bpy.types.volumedisplay.interpolation_method*", "modeling/volumes/properties.html#bpy-types-volumedisplay-interpolation-method"),
("bpy.types.clothsettings.use_pressure_volume*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-use-pressure-volume"),
("bpy.types.clothsettings.vertex_group_intern*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-vertex-group-intern"),
("bpy.types.colormanagedviewsettings.exposure*", "render/color_management.html#bpy-types-colormanagedviewsettings-exposure"),
("bpy.types.cyclesrendersettings.*dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-rate"),
("bpy.types.fluiddomainsettings.cfl_condition*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-cfl-condition"),
("bpy.types.fluiddomainsettings.show_velocity*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-show-velocity"),
@@ -410,6 +421,7 @@ url_manual_mapping = (
("bpy.types.view3doverlay.wireframe_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-wireframe-opacity"),
("bpy.ops.gpencil.active_frames_delete_all*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-active-frames-delete-all"),
("bpy.ops.gpencil.stroke_merge_by_distance*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-stroke-merge-by-distance"),
("bpy.ops.node.collapse_hide_unused_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-collapse-hide-unused-toggle"),
("bpy.ops.object.anim_transforms_to_deltas*", "scene_layout/object/editing/apply.html#bpy-ops-object-anim-transforms-to-deltas"),
("bpy.ops.object.convert_proxy_to_override*", "files/linked_libraries/library_overrides.html#bpy-ops-object-convert-proxy-to-override"),
("bpy.ops.object.modifier_copy_to_selected*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-copy-to-selected"),
@@ -421,6 +433,7 @@ url_manual_mapping = (
("bpy.types.brushgpencilsettings.show_fill*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-show-fill"),
("bpy.types.brushgpencilsettings.uv_random*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-uv-random"),
("bpy.types.clothsettings.internal_tension*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-tension"),
("bpy.types.colormanagedviewsettings.gamma*", "render/color_management.html#bpy-types-colormanagedviewsettings-gamma"),
("bpy.types.compositornodeplanetrackdeform*", "compositing/types/distort/plane_track_deform.html#bpy-types-compositornodeplanetrackdeform"),
("bpy.types.curve.bevel_factor_mapping_end*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-mapping-end"),
("bpy.types.fluiddomainsettings.cache_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-type"),
@@ -475,6 +488,7 @@ url_manual_mapping = (
("bpy.types.brush.multiplane_scrape_angle*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-multiplane-scrape-angle"),
("bpy.types.clothsettings.internal_spring*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-spring"),
("bpy.types.clothsettings.pressure_factor*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-pressure-factor"),
("bpy.types.colormanagedviewsettings.look*", "render/color_management.html#bpy-types-colormanagedviewsettings-look"),
("bpy.types.compositornodecolorcorrection*", "compositing/types/color/color_correction.html#bpy-types-compositornodecolorcorrection"),
("bpy.types.compositornodemoviedistortion*", "compositing/types/distort/movie_distortion.html#bpy-types-compositornodemoviedistortion"),
("bpy.types.fluiddomainsettings.use_guide*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-use-guide"),
@@ -513,6 +527,7 @@ url_manual_mapping = (
("bpy.types.view3doverlay.show_wireframes*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-wireframes"),
("bpy.types.view3dshading.background_type*", "editors/3dview/display/shading.html#bpy-types-view3dshading-background-type"),
("bpy.types.workspace.use_filter_by_owner*", "interface/window_system/workspaces.html#bpy-types-workspace-use-filter-by-owner"),
("bpy.ops.gpencil.image_to_grease_pencil*", "editors/image/editing.html#bpy-ops-gpencil-image-to-grease-pencil"),
("bpy.ops.mesh.vertices_smooth_laplacian*", "modeling/meshes/editing/vertex/laplacian_smooth.html#bpy-ops-mesh-vertices-smooth-laplacian"),
("bpy.ops.object.multires_rebuild_subdiv*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-rebuild-subdiv"),
("bpy.ops.sequencer.select_side_of_frame*", "video_editing/sequencer/selecting.html#bpy-ops-sequencer-select-side-of-frame"),
@@ -575,6 +590,7 @@ url_manual_mapping = (
("bpy.types.brush.texture_overlay_alpha*", "sculpt_paint/brush/cursor.html#bpy-types-brush-texture-overlay-alpha"),
("bpy.types.brushgpencilsettings.random*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random"),
("bpy.types.clothsettings.target_volume*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-target-volume"),
("bpy.types.colormanageddisplaysettings*", "render/color_management.html#bpy-types-colormanageddisplaysettings"),
("bpy.types.compositornodebilateralblur*", "compositing/types/filter/bilateral_blur.html#bpy-types-compositornodebilateralblur"),
("bpy.types.compositornodedistancematte*", "compositing/types/matte/distance_key.html#bpy-types-compositornodedistancematte"),
("bpy.types.compositornodesetalpha.mode*", "compositing/types/converter/set_alpha.html#bpy-types-compositornodesetalpha-mode"),
@@ -660,7 +676,9 @@ url_manual_mapping = (
("bpy.types.shadernodeambientocclusion*", "render/shader_nodes/input/ao.html#bpy-types-shadernodeambientocclusion"),
("bpy.types.shadernodevolumeabsorption*", "render/shader_nodes/shader/volume_absorption.html#bpy-types-shadernodevolumeabsorption"),
("bpy.types.shadernodevolumeprincipled*", "render/shader_nodes/shader/volume_principled.html#bpy-types-shadernodevolumeprincipled"),
("bpy.types.spaceoutliner.display_mode*", "editors/outliner/interface.html#bpy-types-spaceoutliner-display-mode"),
("bpy.types.spaceoutliner.filter_state*", "editors/outliner/interface.html#bpy-types-spaceoutliner-filter-state"),
("bpy.types.toolsettings.keyframe_type*", "editors/timeline.html#bpy-types-toolsettings-keyframe-type"),
("bpy.types.toolsettings.snap_elements*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-snap-elements"),
("bpy.types.toolsettings.use_snap_self*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-self"),
("bpy.types.viewlayer.active_aov_index*", "render/layers/passes.html#bpy-types-viewlayer-active-aov-index"),
@@ -718,6 +736,7 @@ url_manual_mapping = (
("bpy.types.regionview3d.use_box_clip*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-use-box-clip"),
("bpy.types.rendersettings.use_border*", "render/output/properties/dimensions.html#bpy-types-rendersettings-use-border"),
("bpy.types.rigidbodyconstraint.limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-limit"),
("bpy.types.rigidbodyobject.kinematic*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-kinematic"),
("bpy.types.scene.audio_doppler_speed*", "scene_layout/scene/properties.html#bpy-types-scene-audio-doppler-speed"),
("bpy.types.sceneeevee.bokeh_max_size*", "render/eevee/render_settings/depth_of_field.html#bpy-types-sceneeevee-bokeh-max-size"),
("bpy.types.sculpt.detail_type_method*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-type-method"),
@@ -742,7 +761,7 @@ url_manual_mapping = (
("bpy.ops.mesh.select_interior_faces*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-interior-faces"),
("bpy.ops.mesh.select_similar_region*", "modeling/meshes/selecting/similar.html#bpy-ops-mesh-select-similar-region"),
("bpy.ops.mesh.tris_convert_to_quads*", "modeling/meshes/editing/face/triangles_quads.html#bpy-ops-mesh-tris-convert-to-quads"),
("bpy.ops.node.read_fullsamplelayers*", "interface/controls/nodes/editing.html#bpy-ops-node-read-fullsamplelayers"),
("bpy.ops.node.active_preview_toggle*", "modeling/geometry_nodes/introduction.html#bpy-ops-node-active-preview-toggle"),
("bpy.ops.object.datalayout_transfer*", "scene_layout/object/editing/link_transfer/transfer_mesh_data_layout.html#bpy-ops-object-datalayout-transfer"),
("bpy.ops.object.multires_base_apply*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-base-apply"),
("bpy.ops.object.randomize_transform*", "scene_layout/object/editing/transform/randomize.html#bpy-ops-object-randomize-transform"),
@@ -752,6 +771,7 @@ url_manual_mapping = (
("bpy.ops.object.vertex_group_remove*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-remove"),
("bpy.ops.object.vertex_group_smooth*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-smooth"),
("bpy.ops.outliner.collection_enable*", "editors/outliner/editing.html#bpy-ops-outliner-collection-enable"),
("bpy.ops.palette.extract_from_image*", "editors/image/editing.html#bpy-ops-palette-extract-from-image"),
("bpy.ops.pose.user_transforms_clear*", "animation/armatures/posing/editing/clear.html#bpy-ops-pose-user-transforms-clear"),
("bpy.ops.poselib.browse_interactive*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-browse-interactive"),
("bpy.ops.sculpt.set_persistent_base*", "sculpt_paint/sculpting/tools/layer.html#bpy-ops-sculpt-set-persistent-base"),
@@ -766,6 +786,7 @@ url_manual_mapping = (
("bpy.types.brush.use_cursor_overlay*", "sculpt_paint/brush/cursor.html#bpy-types-brush-use-cursor-overlay"),
("bpy.types.camera.show_passepartout*", "render/cameras.html#bpy-types-camera-show-passepartout"),
("bpy.types.collection.lineart_usage*", "scene_layout/collections/properties.html#bpy-types-collection-lineart-usage"),
("bpy.types.colormanagedviewsettings*", "render/color_management.html#bpy-types-colormanagedviewsettings"),
("bpy.types.compositornodebokehimage*", "compositing/types/input/bokeh_image.html#bpy-types-compositornodebokehimage"),
("bpy.types.compositornodecolormatte*", "compositing/types/matte/color_key.html#bpy-types-compositornodecolormatte"),
("bpy.types.compositornodecolorspill*", "compositing/types/matte/color_spill.html#bpy-types-compositornodecolorspill"),
@@ -797,6 +818,7 @@ url_manual_mapping = (
("bpy.types.shadernodebsdfrefraction*", "render/shader_nodes/shader/refraction.html#bpy-types-shadernodebsdfrefraction"),
("bpy.types.shadernodeoutputmaterial*", "render/shader_nodes/output/material.html#bpy-types-shadernodeoutputmaterial"),
("bpy.types.shadernodetexenvironment*", "render/shader_nodes/textures/environment.html#bpy-types-shadernodetexenvironment"),
("bpy.types.spacesequenceeditor.show*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show"),
("bpy.types.spaceuveditor.uv_opacity*", "editors/uv/overlays.html#bpy-types-spaceuveditor-uv-opacity"),
("bpy.types.subdividegpencilmodifier*", "grease_pencil/modifiers/generate/subdivide.html#bpy-types-subdividegpencilmodifier"),
("bpy.types.thicknessgpencilmodifier*", "grease_pencil/modifiers/deform/thickness.html#bpy-types-thicknessgpencilmodifier"),
@@ -868,6 +890,7 @@ url_manual_mapping = (
("bpy.types.multiplygpencilmodifier*", "grease_pencil/modifiers/generate/multiple_strokes.html#bpy-types-multiplygpencilmodifier"),
("bpy.types.rendersettings.filepath*", "render/output/properties/output.html#bpy-types-rendersettings-filepath"),
("bpy.types.rendersettings.fps_base*", "render/output/properties/dimensions.html#bpy-types-rendersettings-fps-base"),
("bpy.types.rigidbodyobject.enabled*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-enabled"),
("bpy.types.sceneeevee.use_overscan*", "render/eevee/render_settings/film.html#bpy-types-sceneeevee-use-overscan"),
("bpy.types.sequencetransform.scale*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-scale"),
("bpy.types.shadernodeeeveespecular*", "render/shader_nodes/shader/specular_bsdf.html#bpy-types-shadernodeeeveespecular"),
@@ -905,6 +928,7 @@ url_manual_mapping = (
("bpy.ops.outliner.collection_hide*", "editors/outliner/editing.html#bpy-ops-outliner-collection-hide"),
("bpy.ops.outliner.collection_show*", "editors/outliner/editing.html#bpy-ops-outliner-collection-show"),
("bpy.ops.paint.mask_lasso_gesture*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask-lasso-gesture"),
("bpy.ops.rigidbody.mass_calculate*", "physics/rigid_body/editing.html#bpy-ops-rigidbody-mass-calculate"),
("bpy.ops.screen.spacedata_cleanup*", "advanced/operators.html#bpy-ops-screen-spacedata-cleanup"),
("bpy.ops.sculpt.detail_flood_fill*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-detail-flood-fill"),
("bpy.ops.sequencer.duplicate_move*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-duplicate-move"),
@@ -979,6 +1003,7 @@ url_manual_mapping = (
("bpy.ops.gpencil.stroke_separate*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-stroke-separate"),
("bpy.ops.gpencil.stroke_simplify*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-simplify"),
("bpy.ops.graph.snap_cursor_value*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-snap-cursor-value"),
("bpy.ops.image.save_all_modified*", "editors/image/editing.html#bpy-ops-image-save-all-modified"),
("bpy.ops.mesh.extrude_edges_move*", "modeling/meshes/editing/edge/extrude_edges.html#bpy-ops-mesh-extrude-edges-move"),
("bpy.ops.mesh.extrude_faces_move*", "modeling/meshes/editing/face/extrude_individual_faces.html#bpy-ops-mesh-extrude-faces-move"),
("bpy.ops.mesh.faces_shade_smooth*", "modeling/meshes/editing/face/shading.html#bpy-ops-mesh-faces-shade-smooth"),
@@ -987,6 +1012,7 @@ url_manual_mapping = (
("bpy.ops.mesh.primitive_cube_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-cube-add"),
("bpy.ops.mesh.primitive_grid_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-grid-add"),
("bpy.ops.mesh.subdivide_edgering*", "modeling/meshes/editing/edge/subdivide_edge_ring.html#bpy-ops-mesh-subdivide-edgering"),
("bpy.ops.node.hide_socket_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-hide-socket-toggle"),
("bpy.ops.node.tree_socket_remove*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-remove"),
("bpy.ops.object.constraints_copy*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-constraints-copy"),
("bpy.ops.object.gpencil_modifier*", "grease_pencil/modifiers/index.html#bpy-ops-object-gpencil-modifier"),
@@ -997,6 +1023,8 @@ url_manual_mapping = (
("bpy.ops.object.vertex_group_add*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-add"),
("bpy.ops.object.vertex_group_fix*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-fix"),
("bpy.ops.outliner.collection_new*", "editors/outliner/editing.html#bpy-ops-outliner-collection-new"),
("bpy.ops.outliner.show_hierarchy*", "editors/outliner/editing.html#bpy-ops-outliner-show-hierarchy"),
("bpy.ops.outliner.show_one_level*", "editors/outliner/editing.html#bpy-ops-outliner-show-one-level"),
("bpy.ops.paint.brush_colors_flip*", "sculpt_paint/texture_paint/tool_settings/brush_settings.html#bpy-ops-paint-brush-colors-flip"),
("bpy.ops.paint.weight_from_bones*", "sculpt_paint/weight_paint/editing.html#bpy-ops-paint-weight-from-bones"),
("bpy.ops.poselib.action_sanitize*", "animation/armatures/properties/pose_library.html#bpy-ops-poselib-action-sanitize"),
@@ -1011,6 +1039,7 @@ url_manual_mapping = (
("bpy.ops.uv.shortest_path_select*", "editors/uv/selecting.html#bpy-ops-uv-shortest-path-select"),
("bpy.ops.wm.operator_cheat_sheet*", "advanced/operators.html#bpy-ops-wm-operator-cheat-sheet"),
("bpy.ops.wm.previews_batch_clear*", "files/blend/previews.html#bpy-ops-wm-previews-batch-clear"),
("bpy.ops.wm.recover_last_session*", "files/blend/open_save.html#bpy-ops-wm-recover-last-session"),
("bpy.types.armature.use_mirror_x*", "animation/armatures/bones/tools/tool_settings.html#bpy-types-armature-use-mirror-x"),
("bpy.types.bakesettings.normal_b*", "render/cycles/baking.html#bpy-types-bakesettings-normal-b"),
("bpy.types.bakesettings.normal_g*", "render/cycles/baking.html#bpy-types-bakesettings-normal-g"),
@@ -1046,6 +1075,7 @@ url_manual_mapping = (
("bpy.types.material.blend_method*", "render/eevee/materials/settings.html#bpy-types-material-blend-method"),
("bpy.types.mirrorgpencilmodifier*", "grease_pencil/modifiers/generate/mirror.html#bpy-types-mirrorgpencilmodifier"),
("bpy.types.movietrackingcamera.k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-k"),
("bpy.types.node.use_custom_color*", "interface/controls/nodes/sidebar.html#bpy-types-node-use-custom-color"),
("bpy.types.offsetgpencilmodifier*", "grease_pencil/modifiers/deform/offset.html#bpy-types-offsetgpencilmodifier"),
("bpy.types.posebone.custom_shape*", "animation/armatures/bones/properties/display.html#bpy-types-posebone-custom-shape"),
("bpy.types.rendersettings.tile_x*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-x"),
@@ -1106,6 +1136,7 @@ url_manual_mapping = (
("bpy.types.compositornoderotate*", "compositing/types/distort/rotate.html#bpy-types-compositornoderotate"),
("bpy.types.compositornodeviewer*", "compositing/types/output/viewer.html#bpy-types-compositornodeviewer"),
("bpy.types.constraint.influence*", "animation/constraints/interface/common.html#bpy-types-constraint-influence"),
("bpy.types.curve.use_path_clamp*", "modeling/curves/properties/path_animation.html#bpy-types-curve-use-path-clamp"),
("bpy.types.datatransfermodifier*", "modeling/modifiers/modify/data_transfer.html#bpy-types-datatransfermodifier"),
("bpy.types.dynamicpaintmodifier*", "physics/dynamic_paint/index.html#bpy-types-dynamicpaintmodifier"),
("bpy.types.ffmpegsettings.audio*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio"),
@@ -1118,6 +1149,7 @@ url_manual_mapping = (
("bpy.types.geometrynodemeshline*", "modeling/geometry_nodes/mesh_primitives/line.html#bpy-types-geometrynodemeshline"),
("bpy.types.gpencillayer.opacity*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-opacity"),
("bpy.types.image.display_aspect*", "editors/image/sidebar.html#bpy-types-image-display-aspect"),
("bpy.types.keyingsetsall.active*", "editors/timeline.html#bpy-types-keyingsetsall-active"),
("bpy.types.limitscaleconstraint*", "animation/constraints/transform/limit_scale.html#bpy-types-limitscaleconstraint"),
("bpy.types.materialgpencilstyle*", "grease_pencil/materials/index.html#bpy-types-materialgpencilstyle"),
("bpy.types.mesh.use_auto_smooth*", "modeling/meshes/structure.html#bpy-types-mesh-use-auto-smooth"),
@@ -1126,6 +1158,7 @@ url_manual_mapping = (
("bpy.types.object.hide_viewport*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-viewport"),
("bpy.types.posebone.rigify_type*", "addons/rigging/rigify/rig_types/index.html#bpy-types-posebone-rigify-type"),
("bpy.types.preferencesfilepaths*", "editors/preferences/file_paths.html#bpy-types-preferencesfilepaths"),
("bpy.types.rigidbodyobject.mass*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-mass"),
("bpy.types.scene.background_set*", "scene_layout/scene/properties.html#bpy-types-scene-background-set"),
("bpy.types.sequence.frame_start*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequence-frame-start"),
("bpy.types.shadernodebackground*", "render/shader_nodes/shader/background.html#bpy-types-shadernodebackground"),
@@ -1159,6 +1192,7 @@ url_manual_mapping = (
("bpy.ops.gpencil.stroke_sample*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-sample"),
("bpy.ops.gpencil.stroke_smooth*", "grease_pencil/modes/edit/point_menu.html#bpy-ops-gpencil-stroke-smooth"),
("bpy.ops.graph.keyframe_insert*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-keyframe-insert"),
("bpy.ops.image.read_viewlayers*", "editors/image/editing.html#bpy-ops-image-read-viewlayers"),
("bpy.ops.mesh.blend_from_shape*", "modeling/meshes/editing/vertex/blend_shape.html#bpy-ops-mesh-blend-from-shape"),
("bpy.ops.mesh.dissolve_limited*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve-limited"),
("bpy.ops.mesh.face_make_planar*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-face-make-planar"),
@@ -1166,6 +1200,7 @@ url_manual_mapping = (
("bpy.ops.mesh.faces_shade_flat*", "modeling/meshes/editing/face/shading.html#bpy-ops-mesh-faces-shade-flat"),
("bpy.ops.mesh.paint_mask_slice*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-mesh-paint-mask-slice"),
("bpy.ops.mesh.select_ungrouped*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-ungrouped"),
("bpy.ops.node.delete_reconnect*", "interface/controls/nodes/editing.html#bpy-ops-node-delete-reconnect"),
("bpy.ops.node.tree_path_parent*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-path-parent"),
("bpy.ops.node.tree_socket_move*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-move"),
("bpy.ops.object.duplicate_move*", "scene_layout/object/editing/duplicate.html#bpy-ops-object-duplicate-move"),
@@ -1274,6 +1309,8 @@ url_manual_mapping = (
("bpy.ops.mesh.delete_edgeloop*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-delete-edgeloop"),
("bpy.ops.mesh.vertices_smooth*", "modeling/meshes/editing/vertex/smooth_vertices.html#bpy-ops-mesh-vertices-smooth"),
("bpy.ops.nla.make_single_user*", "editors/nla/editing.html#bpy-ops-nla-make-single-user"),
("bpy.ops.node.clipboard_paste*", "interface/controls/nodes/editing.html#bpy-ops-node-clipboard-paste"),
("bpy.ops.node.node_copy_color*", "interface/controls/nodes/sidebar.html#bpy-ops-node-node-copy-color"),
("bpy.ops.node.read_viewlayers*", "interface/controls/nodes/editing.html#bpy-ops-node-read-viewlayers"),
("bpy.ops.node.tree_socket_add*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-add"),
("bpy.ops.object.data_transfer*", "scene_layout/object/editing/link_transfer/transfer_mesh_data.html#bpy-ops-object-data-transfer"),
@@ -1281,6 +1318,8 @@ url_manual_mapping = (
("bpy.ops.object.select_linked*", "scene_layout/object/selecting.html#bpy-ops-object-select-linked"),
("bpy.ops.object.select_mirror*", "scene_layout/object/selecting.html#bpy-ops-object-select-mirror"),
("bpy.ops.object.select_random*", "scene_layout/object/selecting.html#bpy-ops-object-select-random"),
("bpy.ops.object.transfer_mode*", "sculpt_paint/sculpting/editing/sculpt.html#bpy-ops-object-transfer-mode"),
("bpy.ops.outliner.show_active*", "editors/outliner/editing.html#bpy-ops-outliner-show-active"),
("bpy.ops.paint.add_simple_uvs*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-ops-paint-add-simple-uvs"),
("bpy.ops.pose.rigify_generate*", "addons/rigging/rigify/basics.html#bpy-ops-pose-rigify-generate"),
("bpy.ops.preferences.autoexec*", "editors/preferences/save_load.html#bpy-ops-preferences-autoexec"),
@@ -1297,6 +1336,7 @@ url_manual_mapping = (
("bpy.ops.uv.project_from_view*", "modeling/meshes/editing/uv.html#bpy-ops-uv-project-from-view"),
("bpy.ops.wm.blenderkit_logout*", "addons/3d_view/blenderkit.html#bpy-ops-wm-blenderkit-logout"),
("bpy.ops.wm.memory_statistics*", "advanced/operators.html#bpy-ops-wm-memory-statistics"),
("bpy.ops.wm.recover_auto_save*", "files/blend/open_save.html#bpy-ops-wm-recover-auto-save"),
("bpy.types.adjustmentsequence*", "video_editing/sequencer/strips/adjustment.html#bpy-types-adjustmentsequence"),
("bpy.types.alphaundersequence*", "video_editing/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaundersequence"),
("bpy.types.armature.show_axes*", "animation/armatures/properties/display.html#bpy-types-armature-show-axes"),
@@ -1356,6 +1396,7 @@ url_manual_mapping = (
("bpy.ops.anim.keyframe_clear*", "animation/keyframes/editing.html#bpy-ops-anim-keyframe-clear"),
("bpy.ops.armature.flip_names*", "animation/armatures/bones/editing/naming.html#bpy-ops-armature-flip-names"),
("bpy.ops.armature.select_all*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-all"),
("bpy.ops.clip.average_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-average-tracks"),
("bpy.ops.clip.refine_markers*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-refine-markers"),
("bpy.ops.clip.select_grouped*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-grouped"),
("bpy.ops.clip.track_to_empty*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-track-to-empty"),
@@ -1369,6 +1410,7 @@ url_manual_mapping = (
("bpy.ops.gpencil.stroke_join*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-join"),
("bpy.ops.gpencil.stroke_trim*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-trim"),
("bpy.ops.gpencil.trace_image*", "grease_pencil/modes/object/trace_image.html#bpy-ops-gpencil-trace-image"),
("bpy.ops.image.external_edit*", "editors/image/editing.html#bpy-ops-image-external-edit"),
("bpy.ops.mesh.colors_reverse*", "modeling/meshes/editing/face/face_data.html#bpy-ops-mesh-colors-reverse"),
("bpy.ops.mesh.dissolve_edges*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve-edges"),
("bpy.ops.mesh.dissolve_faces*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve-faces"),
@@ -1383,6 +1425,10 @@ url_manual_mapping = (
("bpy.ops.mesh.smooth_normals*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-smooth-normals"),
("bpy.ops.nla.action_pushdown*", "editors/nla/tracks.html#bpy-ops-nla-action-pushdown"),
("bpy.ops.nla.tweakmode_enter*", "editors/nla/editing.html#bpy-ops-nla-tweakmode-enter"),
("bpy.ops.node.clipboard_copy*", "interface/controls/nodes/editing.html#bpy-ops-node-clipboard-copy"),
("bpy.ops.node.duplicate_move*", "interface/controls/nodes/editing.html#bpy-ops-node-duplicate-move"),
("bpy.ops.node.options_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-options-toggle"),
("bpy.ops.node.preview_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-preview-toggle"),
("bpy.ops.object.origin_clear*", "scene_layout/object/editing/clear.html#bpy-ops-object-origin-clear"),
("bpy.ops.object.parent_clear*", "scene_layout/object/editing/parent.html#bpy-ops-object-parent-clear"),
("bpy.ops.object.shade_smooth*", "scene_layout/object/editing/shading.html#bpy-ops-object-shade-smooth"),
@@ -1403,6 +1449,7 @@ url_manual_mapping = (
("bpy.ops.uv.minimize_stretch*", "modeling/meshes/uv/editing.html#bpy-ops-uv-minimize-stretch"),
("bpy.ops.uv.select_edge_ring*", "editors/uv/selecting.html#bpy-ops-uv-select-edge-ring"),
("bpy.ops.wm.blenderkit_login*", "addons/3d_view/blenderkit.html#bpy-ops-wm-blenderkit-login"),
("bpy.ops.wm.save_as_mainfile*", "files/blend/open_save.html#bpy-ops-wm-save-as-mainfile"),
("bpy.types.alphaoversequence*", "video_editing/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaoversequence"),
("bpy.types.armatureeditbones*", "animation/armatures/bones/editing/index.html#bpy-types-armatureeditbones"),
("bpy.types.brush.pose_offset*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-pose-offset"),
@@ -1642,11 +1689,15 @@ url_manual_mapping = (
("bpy.ops.mesh.edge_rotate*", "modeling/meshes/editing/edge/rotate_edge.html#bpy-ops-mesh-edge-rotate"),
("bpy.ops.mesh.unsubdivide*", "modeling/meshes/editing/edge/unsubdivide.html#bpy-ops-mesh-unsubdivide"),
("bpy.ops.mesh.uvs_reverse*", "modeling/meshes/uv/editing.html#bpy-ops-mesh-uvs-reverse"),
("bpy.ops.node.hide_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-hide-toggle"),
("bpy.ops.node.mute_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-mute-toggle"),
("bpy.ops.object.hide_view*", "scene_layout/object/editing/show_hide.html#bpy-ops-object-hide-view"),
("bpy.ops.object.track_set*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-track-set"),
("bpy.ops.pose.scale_clear*", "animation/armatures/posing/editing/clear.html#bpy-ops-pose-scale-clear"),
("bpy.ops.poselib.pose_add*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-pose-add"),
("bpy.ops.scene.view_layer*", "render/layers/introduction.html#bpy-ops-scene-view-layer"),
("bpy.ops.screen.redo_last*", "interface/undo_redo.html#bpy-ops-screen-redo-last"),
("bpy.ops.sculpt.mask_init*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-sculpt-mask-init"),
("bpy.ops.sequencer.delete*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-delete"),
("bpy.ops.sequencer.reload*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-reload"),
("bpy.ops.sequencer.unlock*", "video_editing/sequencer/editing.html#bpy-ops-sequencer-unlock"),
@@ -1659,7 +1710,9 @@ url_manual_mapping = (
("bpy.ops.uv.snap_selected*", "modeling/meshes/uv/editing.html#bpy-ops-uv-snap-selected"),
("bpy.ops.view3d.localview*", "editors/3dview/navigate/local_view.html#bpy-ops-view3d-localview"),
("bpy.ops.view3d.view_axis*", "editors/3dview/navigate/viewpoint.html#bpy-ops-view3d-view-axis"),
("bpy.ops.wm.open_mainfile*", "files/blend/open_save.html#bpy-ops-wm-open-mainfile"),
("bpy.ops.wm.owner_disable*", "interface/window_system/workspaces.html#bpy-ops-wm-owner-disable"),
("bpy.ops.wm.save_mainfile*", "files/blend/open_save.html#bpy-ops-wm-save-mainfile"),
("bpy.types.bone.show_wire*", "animation/armatures/bones/properties/display.html#bpy-types-bone-show-wire"),
("bpy.types.brush.hardness*", "sculpt_paint/sculpting/tool_settings/brush_settings.html#bpy-types-brush-hardness"),
("bpy.types.curvesmodifier*", "video_editing/sequencer/sidebar/modifiers.html#bpy-types-curvesmodifier"),
@@ -1715,6 +1768,7 @@ url_manual_mapping = (
("bpy.ops.nla.clear_scale*", "editors/nla/editing.html#bpy-ops-nla-clear-scale"),
("bpy.ops.nla.mute_toggle*", "editors/nla/editing.html#bpy-ops-nla-mute-toggle"),
("bpy.ops.node.group_make*", "interface/controls/nodes/groups.html#bpy-ops-node-group-make"),
("bpy.ops.node.links_mute*", "interface/controls/nodes/editing.html#bpy-ops-node-links-mute"),
("bpy.ops.object.armature*", "animation/armatures/index.html#bpy-ops-object-armature"),
("bpy.ops.object.face_map*", "modeling/meshes/properties/object_data.html#bpy-ops-object-face-map"),
("bpy.ops.object.join_uvs*", "scene_layout/object/editing/link_transfer/copy_uvmaps.html#bpy-ops-object-join-uvs"),
@@ -1794,6 +1848,8 @@ url_manual_mapping = (
("bpy.ops.mesh.polybuild*", "modeling/meshes/tools/poly_build.html#bpy-ops-mesh-polybuild"),
("bpy.ops.mesh.subdivide*", "modeling/meshes/editing/edge/subdivide.html#bpy-ops-mesh-subdivide"),
("bpy.ops.mesh.wireframe*", "modeling/meshes/editing/face/wireframe.html#bpy-ops-mesh-wireframe"),
("bpy.ops.node.link_make*", "interface/controls/nodes/editing.html#bpy-ops-node-link-make"),
("bpy.ops.node.links_cut*", "interface/controls/nodes/editing.html#bpy-ops-node-links-cut"),
("bpy.ops.object.convert*", "scene_layout/object/editing/convert.html#bpy-ops-object-convert"),
("bpy.ops.object.gpencil*", "grease_pencil/index.html#bpy-ops-object-gpencil"),
("bpy.ops.object.speaker*", "render/output/audio/speaker.html#bpy-ops-object-speaker"),
@@ -1815,7 +1871,6 @@ url_manual_mapping = (
("bpy.types.blendtexture*", "render/materials/legacy_textures/types/blend.html#bpy-types-blendtexture"),
("bpy.types.brush.height*", "sculpt_paint/sculpting/tools/layer.html#bpy-types-brush-height"),
("bpy.types.castmodifier*", "modeling/modifiers/deform/cast.html#bpy-types-castmodifier"),
("bpy.types.colormanaged*", "render/color_management.html#bpy-types-colormanaged"),
("bpy.types.curve.offset*", "modeling/curves/properties/geometry.html#bpy-types-curve-offset"),
("bpy.types.geometrynode*", "modeling/geometry_nodes/index.html#bpy-types-geometrynode"),
("bpy.types.glowsequence*", "video_editing/sequencer/strips/effects/glow.html#bpy-types-glowsequence"),
@@ -1855,6 +1910,8 @@ url_manual_mapping = (
("bpy.ops.file.pack_all*", "files/blend/packed_data.html#bpy-ops-file-pack-all"),
("bpy.ops.gpencil.paste*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-paste"),
("bpy.ops.image.project*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-ops-image-project"),
("bpy.ops.image.replace*", "editors/image/editing.html#bpy-ops-image-replace"),
("bpy.ops.image.save_as*", "editors/image/editing.html#bpy-ops-image-save-as"),
("bpy.ops.material.copy*", "render/materials/assignment.html#bpy-ops-material-copy"),
("bpy.ops.mesh.decimate*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-decimate"),
("bpy.ops.mesh.dissolve*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve"),
@@ -1907,6 +1964,10 @@ url_manual_mapping = (
("bpy.ops.graph.sample*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-sample"),
("bpy.ops.graph.smooth*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-smooth"),
("bpy.ops.graph.unbake*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-unbake"),
("bpy.ops.image.invert*", "editors/image/editing.html#bpy-ops-image-invert"),
("bpy.ops.image.reload*", "editors/image/editing.html#bpy-ops-image-reload"),
("bpy.ops.image.resize*", "editors/image/editing.html#bpy-ops-image-resize"),
("bpy.ops.image.unpack*", "editors/image/editing.html#bpy-ops-image-unpack"),
("bpy.ops.material.new*", "render/materials/assignment.html#bpy-ops-material-new"),
("bpy.ops.object.align*", "scene_layout/object/editing/transform/align_objects.html#bpy-ops-object-align"),
("bpy.ops.object.empty*", "modeling/empties.html#bpy-ops-object-empty"),
@@ -1921,6 +1982,8 @@ url_manual_mapping = (
("bpy.types.constraint*", "animation/constraints/index.html#bpy-types-constraint"),
("bpy.types.imagepaint*", "sculpt_paint/texture_paint/index.html#bpy-types-imagepaint"),
("bpy.types.lightprobe*", "render/eevee/light_probes/index.html#bpy-types-lightprobe"),
("bpy.types.node.color*", "interface/controls/nodes/sidebar.html#bpy-types-node-color"),
("bpy.types.node.label*", "interface/controls/nodes/sidebar.html#bpy-types-node-label"),
("bpy.types.nodesocket*", "interface/controls/nodes/parts.html#bpy-types-nodesocket"),
("bpy.types.paint.tile*", "sculpt_paint/texture_paint/tool_settings/tiling.html#bpy-types-paint-tile"),
("bpy.types.pointcache*", "physics/baking.html#bpy-types-pointcache"),
@@ -1935,6 +1998,7 @@ url_manual_mapping = (
("bpy.ops.mesh.bisect*", "modeling/meshes/editing/mesh/bisect.html#bpy-ops-mesh-bisect"),
("bpy.ops.mesh.delete*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-delete"),
("bpy.ops.nla.move_up*", "editors/nla/editing.html#bpy-ops-nla-move-up"),
("bpy.ops.node.delete*", "interface/controls/nodes/editing.html#bpy-ops-node-delete"),
("bpy.ops.object.bake*", "render/cycles/baking.html#bpy-ops-object-bake"),
("bpy.ops.object.hook*", "modeling/meshes/editing/vertex/hooks.html#bpy-ops-object-hook"),
("bpy.ops.object.join*", "scene_layout/object/editing/join.html#bpy-ops-object-join"),
@@ -1954,6 +2018,7 @@ url_manual_mapping = (
("bpy.types.fmodifier*", "editors/graph_editor/fcurves/sidebar/modifiers.html#bpy-types-fmodifier"),
("bpy.types.freestyle*", "render/freestyle/index.html#bpy-types-freestyle"),
("bpy.types.movieclip*", "movie_clip/index.html#bpy-types-movieclip"),
("bpy.types.node.name*", "interface/controls/nodes/sidebar.html#bpy-types-node-name"),
("bpy.types.nodeframe*", "interface/controls/nodes/frame.html#bpy-types-nodeframe"),
("bpy.types.nodegroup*", "interface/controls/nodes/groups.html#bpy-types-nodegroup"),
("bpy.types.spotlight*", "render/lights/light_object.html#bpy-types-spotlight"),
@@ -1971,6 +2036,10 @@ url_manual_mapping = (
("bpy.ops.graph.copy*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-copy"),
("bpy.ops.graph.hide*", "editors/graph_editor/channels.html#bpy-ops-graph-hide"),
("bpy.ops.graph.snap*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-snap"),
("bpy.ops.image.flip*", "editors/image/editing.html#bpy-ops-image-flip"),
("bpy.ops.image.open*", "editors/image/editing.html#bpy-ops-image-open"),
("bpy.ops.image.pack*", "editors/image/editing.html#bpy-ops-image-pack"),
("bpy.ops.image.save*", "editors/image/editing.html#bpy-ops-image-save"),
("bpy.ops.image.tile*", "modeling/meshes/uv/workflows/udims.html#bpy-ops-image-tile"),
("bpy.ops.mesh.bevel*", "modeling/meshes/editing/edge/bevel.html#bpy-ops-mesh-bevel"),
("bpy.ops.mesh.inset*", "modeling/meshes/editing/face/inset_faces.html#bpy-ops-mesh-inset"),
@@ -2002,6 +2071,7 @@ url_manual_mapping = (
("bpy.types.spacenla*", "editors/nla/index.html#bpy-types-spacenla"),
("bpy.types.sunlight*", "render/lights/light_object.html#bpy-types-sunlight"),
("bpy.ops.clip.open*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-open"),
("bpy.ops.image.new*", "editors/image/editing.html#bpy-ops-image-new"),
("bpy.ops.mesh.fill*", "modeling/meshes/editing/face/fill.html#bpy-ops-mesh-fill"),
("bpy.ops.mesh.poke*", "modeling/meshes/editing/face/poke_faces.html#bpy-ops-mesh-poke"),
("bpy.ops.mesh.spin*", "modeling/meshes/tools/spin.html#bpy-ops-mesh-spin"),

View File

@@ -103,8 +103,8 @@ class Prefs(bpy.types.KeyConfigPreferences):
v3d_tilde_action: EnumProperty(
name="Tilde Action",
items=(
('VIEW', "Navigate",
"View operations (useful for keyboards without a numpad)",
('OBJECT_SWITCH', "Object Switch",
"Switch the active object under the cursor (when not in object mode)",
0),
('GIZMO', "Gizmos",
"Control transform gizmos",
@@ -113,7 +113,7 @@ class Prefs(bpy.types.KeyConfigPreferences):
description=(
"Action when 'Tilde' is pressed"
),
default='VIEW',
default='OBJECT_SWITCH',
update=update_fn,
)

View File

@@ -1078,17 +1078,11 @@ def km_view3d(params):
{"properties": [("use_all_regions", True), ("center", False)]}),
("view3d.view_all", {"type": 'C', "value": 'PRESS', "shift": True},
{"properties": [("center", True)]}),
op_menu_pie(
"VIEW3D_MT_view_pie" if params.v3d_tilde_action == 'VIEW' else "VIEW3D_MT_transform_gizmo_pie",
{"type": 'ACCENT_GRAVE', "value": params.pie_value},
),
*(() if not params.use_pie_click_drag else
(("view3d.navigate", {"type": 'ACCENT_GRAVE', "value": 'CLICK'}, None),)),
("view3d.navigate", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "shift": True}, None),
op_menu_pie("VIEW3D_MT_view_pie", {"type": 'D', "value": 'CLICK_DRAG'}),
# Numpad views.
("view3d.view_camera", {"type": 'NUMPAD_0', "value": 'PRESS'}, None),
("view3d.view_axis", {"type": 'NUMPAD_1', "value": 'PRESS'},
{"properties": [("type", 'FRONT')]}),
("view3d.view_axis", {"type": 'NUMPAD_1', "value": 'PRESS'},
{"properties": [("type", 'FRONT')]}),
("view3d.view_orbit", {"type": 'NUMPAD_2', "value": 'PRESS', "repeat": True},
{"properties": [("type", 'ORBITDOWN')]}),
("view3d.view_axis", {"type": 'NUMPAD_3', "value": 'PRESS'},
@@ -1327,6 +1321,32 @@ def km_view3d(params):
op_tool_cycle("builtin.select_box", {"type": 'W', "value": 'PRESS'}),
])
# Tilda key.
if params.use_pie_click_drag:
items.extend([
("object.transfer_mode",
{"type": 'ACCENT_GRAVE', "value": 'CLICK' if params.use_pie_click_drag else 'PRESS'},
None),
op_menu_pie(
"VIEW3D_MT_transform_gizmo_pie",
{"type": 'ACCENT_GRAVE', "value": 'CLICK_DRAG'},
)
])
else:
if params.v3d_tilde_action == 'OBJECT_SWITCH':
items.append(
("object.transfer_mode",
{"type": 'ACCENT_GRAVE', "value": 'PRESS'},
{"properties": [("use_eyedropper", False)]})
)
else:
items.append(
op_menu_pie(
"VIEW3D_MT_transform_gizmo_pie",
{"type": 'ACCENT_GRAVE', "value": 'PRESS'},
)
)
return keymap
@@ -4484,8 +4504,6 @@ def km_sculpt(params):
)
items.extend([
# Transfer Sculpt Mode (release to avoid conflict with grease pencil drawing).
("object.transfer_mode", {"type": 'D', "value": 'RELEASE'}, None),
# Brush strokes
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("mode", 'NORMAL')]}),

View File

@@ -42,8 +42,8 @@ def geometry_node_group_empty_new():
def geometry_modifier_poll(context):
ob = context.object
# Test object support for geometry node modifier (No volume, curve, or hair object support yet)
if not ob or ob.type not in {'MESH', 'POINTCLOUD'}:
# Test object support for geometry node modifier (No curve, or hair object support yet)
if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME'}:
return False
return True

View File

@@ -306,64 +306,6 @@ class NODE_OT_tree_path_parent(Operator):
return {'FINISHED'}
class NODE_OT_active_preview_toggle(Operator):
'''Toggle active preview state of node'''
bl_idname = "node.active_preview_toggle"
bl_label = "Toggle Active Preview"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
space = context.space_data
if space is None:
return False
if space.type != 'NODE_EDITOR':
return False
if space.edit_tree is None:
return False
if space.edit_tree.nodes.active is None:
return False
return True
def execute(self, context):
node_editor = context.space_data
ntree = node_editor.edit_tree
active_node = ntree.nodes.active
if active_node.active_preview:
self.disable_preview(context, ntree, active_node)
else:
self.enable_preview(context, node_editor, ntree, active_node)
return {'FINISHED'}
def enable_preview(self, context, node_editor, ntree, active_node):
spreadsheets = self.find_unpinned_spreadsheets(context)
for spreadsheet in spreadsheets:
spreadsheet.set_geometry_node_context(node_editor, active_node)
for node in ntree.nodes:
node.active_preview = False
active_node.active_preview = True
def disable_preview(self, context, ntree, active_node):
spreadsheets = self.find_unpinned_spreadsheets(context)
for spreadsheet in spreadsheets:
spreadsheet.context_path.clear()
active_node.active_preview = False
def find_unpinned_spreadsheets(self, context):
spreadsheets = []
for window in context.window_manager.windows:
for area in window.screen.areas:
space = area.spaces.active
if space.type == 'SPREADSHEET' and not space.is_pinned:
spreadsheets.append(space)
return spreadsheets
classes = (
NodeSetting,
@@ -372,5 +314,4 @@ classes = (
NODE_OT_add_search,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_tree_path_parent,
NODE_OT_active_preview_toggle,
)

View File

@@ -56,7 +56,7 @@ class SPREADSHEET_OT_toggle_pin(Operator):
for node_editor in node_editors:
ntree = node_editor.edit_tree
for node in ntree.nodes:
if node.active_preview:
if node.bl_idname == "GeometryNodeViewer":
space.set_geometry_node_context(node_editor, node)
return

View File

@@ -153,7 +153,7 @@ class PREFERENCES_OT_copy_prev(Operator):
def execute(self, _context):
import shutil
shutil.copytree(self._old_path(), self._new_path(), dirs_exist_ok=True)
shutil.copytree(self._old_path(), self._new_path(), dirs_exist_ok=True, symlinks=True)
# reload preferences and recent-files.txt
bpy.ops.wm.read_userpref()

View File

@@ -628,7 +628,7 @@ class LightMapPack(Operator):
name="New Image",
description=(
"Assign new images for every mesh (only one if "
"shared tex space enabled)"
"Share Texture Space is enabled)"
),
default=False,
)

View File

@@ -407,7 +407,7 @@ class AnnotationDataPanel:
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
if context.space_data.type not in {'VIEW_3D', 'TOPBAR'}:
if context.space_data.type not in {'VIEW_3D', 'TOPBAR', 'SEQUENCE_EDITOR'}:
self.layout.prop(context.space_data, "show_annotation", text="")
def draw(self, context):
@@ -857,6 +857,10 @@ class GreasePencilLayerRelationsPanel:
col = layout.row(align=True)
col.prop_search(gpl, "viewlayer_render", scene, "view_layers", text="View Layer")
col = layout.row(align=True)
# Only enable this property when a view layer is selected.
col.enabled = bool(gpl.viewlayer_render)
col.prop(gpl, "use_viewlayer_masks")
class GreasePencilLayerDisplayPanel:

View File

@@ -660,8 +660,12 @@ class NODE_PT_quality(bpy.types.Panel):
snode = context.space_data
tree = snode.node_tree
prefs = bpy.context.preferences
col = layout.column()
if prefs.experimental.use_full_frame_compositor:
col.prop(tree, "execution_mode")
col.prop(tree, "render_quality", text="Render")
col.prop(tree, "edit_quality", text="Edit")
col.prop(tree, "chunk_size")

View File

@@ -1398,8 +1398,8 @@ class SEQUENCER_PT_source(SequencerButtonsPanel, Panel):
box.template_image_stereo_3d(strip.stereo_3d_format)
# Resolution.
col = layout.column(align=True)
col = col.box()
col = layout.box()
col = col.column(align=True)
split = col.split(factor=0.5, align=False)
split.alignment = 'RIGHT'
split.label(text="Resolution")
@@ -1409,6 +1409,14 @@ class SEQUENCER_PT_source(SequencerButtonsPanel, Panel):
split.label(text="%dx%d" % size, translate=False)
else:
split.label(text="None")
#FPS
if elem.orig_fps:
split = col.split(factor=0.5, align=False)
split.alignment = 'RIGHT'
split.label(text="FPS")
split.alignment = 'LEFT'
split.label(text="%.2f" % elem.orig_fps, translate=False)
class SEQUENCER_PT_scene(SequencerButtonsPanel, Panel):

View File

@@ -164,7 +164,7 @@ class _defs_annotate:
gpl = context.active_annotation_layer
if gpl is not None:
layout.label(text="Annotation:")
if context.space_data.type == 'VIEW_3D':
if context.space_data.type in {'VIEW_3D', 'SEQUENCE_EDITOR'}:
if region_type == 'TOOL_HEADER':
sub = layout.split(align=True, factor=0.5)
sub.ui_units_x = 6.5
@@ -206,14 +206,22 @@ class _defs_annotate:
col = layout.row().column(align=True)
col.prop(props, "arrowstyle_start", text="Style Start")
col.prop(props, "arrowstyle_end", text="End")
elif tool.idname == "builtin.annotate" and region_type != 'TOOL_HEADER':
layout.separator()
elif tool.idname == "builtin.annotate":
props = tool.operator_properties("gpencil.annotate")
layout.prop(props, "use_stabilizer", text="Stabilize Stroke")
col = layout.column(align=False)
col.active = props.use_stabilizer
col.prop(props, "stabilizer_radius", text="Radius", slider=True)
col.prop(props, "stabilizer_factor", text="Factor", slider=True)
if region_type == 'TOOL_HEADER':
row = layout.row()
row.prop(props, "use_stabilizer", text="Stabilize Stroke")
subrow = layout.row(align=False)
subrow.active = props.use_stabilizer
subrow.prop(props, "stabilizer_radius", text="Radius", slider=True)
subrow.prop(props, "stabilizer_factor", text="Factor", slider=True)
else:
layout.separator()
layout.prop(props, "use_stabilizer", text="Stabilize Stroke")
col = layout.column(align=False)
col.active = props.use_stabilizer
col.prop(props, "stabilizer_radius", text="Radius", slider=True)
col.prop(props, "stabilizer_factor", text="Factor", slider=True)
@ToolDef.from_fn.with_args(draw_settings=draw_settings_common)
def scribble(*, draw_settings):

View File

@@ -2256,6 +2256,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
context, (
({"property": "use_new_hair_type"}, "T68981"),
({"property": "use_new_point_cloud_type"}, "T75717"),
({"property": "use_full_frame_compositor"}, "T88150"),
),
)

View File

@@ -2314,6 +2314,7 @@ class VIEW3D_MT_object_animation(Menu):
layout.operator("nla.bake", text="Bake Action...")
layout.operator("gpencil.bake_mesh_animation", text="Bake Mesh to Grease Pencil...")
layout.operator("gpencil.bake_grease_pencil_animation", text="Bake Object Transform to Grease Pencil...")
class VIEW3D_MT_object_rigid_body(Menu):

View File

@@ -495,6 +495,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeAttributeTransfer"),
]),
GeometryNodeCategory("GEO_COLOR", "Color", items=[
NodeItem("ShaderNodeRGBCurve"),
NodeItem("ShaderNodeValToRGB"),
NodeItem("ShaderNodeSeparateRGB"),
NodeItem("ShaderNodeCombineRGB"),
@@ -502,9 +503,11 @@ geometry_node_categories = [
GeometryNodeCategory("GEO_CURVE", "Curve", items=[
NodeItem("GeometryNodeCurveToMesh"),
NodeItem("GeometryNodeCurveResample"),
NodeItem("GeometryNodeMeshToCurve"),
]),
GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[
NodeItem("GeometryNodeBoundBox"),
NodeItem("GeometryNodeDeleteGeometry"),
NodeItem("GeometryNodeTransform"),
NodeItem("GeometryNodeJoinGeometry"),
]),
@@ -557,11 +560,15 @@ geometry_node_categories = [
NodeItem("GeometryNodeSwitch"),
]),
GeometryNodeCategory("GEO_VECTOR", "Vector", items=[
NodeItem("ShaderNodeVectorCurve"),
NodeItem("ShaderNodeSeparateXYZ"),
NodeItem("ShaderNodeCombineXYZ"),
NodeItem("ShaderNodeVectorMath"),
NodeItem("ShaderNodeVectorRotate"),
]),
GeometryNodeCategory("GEO_OUTPUT", "Output", items=[
NodeItem("GeometryNodeViewer"),
]),
GeometryNodeCategory("GEO_VOLUME", "Volume", items=[
NodeItem("GeometryNodePointsToVolume"),
NodeItem("GeometryNodeVolumeToMesh"),

View File

@@ -1,70 +0,0 @@
Creating Steam builds for Blender
=================================
This script automates creation of the Steam files: download of the archives,
extraction of the archives, preparation of the build scripts (VDF files), actual
building of the Steam game files.
Requirements
============
* MacOS machine - Tested on Catalina 10.15.6. Extracting contents from the DMG
archive did not work Windows nor on Linux using 7-zip. All DMG archives tested
failed to be extracted. As such only MacOS is known to work.
* Steam SDK downloaded from SteamWorks - The `steamcmd` is used to generate the
Steam game files. The path to the `steamcmd` is what is actually needed.
* SteamWorks credentials - Needed to log in using `steamcmd`.
* Login to SteamWorks with the `steamcmd` from the command-line at least once -
Needded to ensure the user is properly logged in. On a new machine the user
will have to go through two-factor authentication.
* App ID and Depot IDs - Needed to create the VDF files.
* Python 3.x - 3.7 was tested.
* Base URL - for downloading the archives.
Usage
=====
```bash
$ export STEAMUSER=SteamUserName
$ export STEAMPW=SteamUserPW
$ export BASEURL=https://download.blender.org/release/Blender2.83/
$ export VERSION=2.83.3
$ export APPID=appidnr
$ export WINID=winidnr
$ export LINID=linuxidnr
$ export MACOSID=macosidnr
# log in to SteamWorks from command-line at least once
$ ../sdk/tools/ContentBuilder/builder_osx/steamcmd +login $STEAMUSER $STEAMPW
# once that has been done we can now actually start our tool
$ python3.7 create_steam_builds.py --baseurl $BASEURL --version $VERSION --appid $APPID --winid $WINID --linuxid $LINID --macosid $MACOSID --steamuser $STEAMUSER --steampw $STEAMPW --steamcmd ../sdk/tools/ContentBuilder/builder_osx/steamcmd
```
All arguments in the above example are required.
At the start the tool will login using `steamcmd`. This is necessary to let the
Steam SDK update itself if necessary.
There are a few optional arguments:
* `--dryrun`: If set building the game files will not actually happen. A set of
log files and a preview manifest per depot will be created in the output folder.
This can be used to double-check everything works as expected.
* `--skipdl`: If set will skip downloading of the archives. The tool expects the
archives to already exist in the correct content location.
* `--skipextract`: If set will skip extraction of all archives. The tool expects
the archives to already have been correctly extracted in the content location.
Run the tool with `-h` for detailed information on each argument.
The content and output folders are generated through appending the version
without dots to the words `content` and `output` respectively, e.g. `content2833`
and `output2833`. These folders are created next to the tool.
From all `.template` files the Steam build scripts will be generated also in the
same directory as the tool. The files will have the extension `.vdf`.
In case of errors the tool will have a non-zero return code.

View File

@@ -1,17 +0,0 @@
"appbuild"
{
"appid" "[APPID]"
"desc" "Blender [VERSION]" // description for this build
"buildoutput" "./[OUTPUT]" // build output folder for .log, .csm & .csd files, relative to location of this file
"contentroot" "./[CONTENT]" // root content folder, relative to location of this file
"setlive" "" // branch to set live after successful build, non if empty
"preview" "[DRYRUN]" // 1 to enable preview builds, 0 to commit build to steampipe
"local" "" // set to flie path of local content server
"depots"
{
"[WINID]" "depot_build_win.vdf"
"[LINUXID]" "depot_build_linux.vdf"
"[MACOSID]" "depot_build_macos.vdf"
}
}

View File

@@ -1,397 +0,0 @@
#!/usr/bin/env python3
import argparse
import pathlib
import requests
import shutil
import subprocess
from typing import Callable, Iterator, List, Tuple
# supported archive and platform endings, used to create actual archive names
archive_endings = ["windows64.zip", "linux64.tar.xz", "macOS.dmg"]
def add_optional_argument(option: str, help: str) -> None:
global parser
"""Add an optional argument
Args:
option (str): Option to add
help (str): Help description for the argument
"""
parser.add_argument(option, help=help, action='store_const', const=1)
def blender_archives(version: str) -> Iterator[str]:
"""Generator for Blender archives for version.
Yields for items in archive_endings an archive name in the form of
blender-{version}-{ending}.
Args:
version (str): Version string of the form 2.83.2
Yields:
Iterator[str]: Name in the form of blender-{version}-{ending}
"""
global archive_endings
for ending in archive_endings:
yield f"blender-{version}-{ending}"
def get_archive_type(archive_type: str, version: str) -> str:
"""Return the archive of given type and version.
Args:
archive_type (str): extension for archive type to check for
version (str): Version string in the form 2.83.2
Raises:
Exception: Execption when archive type isn't found
Returns:
str: archive name for given type
"""
for archive in blender_archives(version):
if archive.endswith(archive_type):
return archive
raise Exception("Unknown archive type")
def execute_command(cmd: List[str], name: str, errcode: int, cwd=".", capture_output=True) -> str:
"""Execute the given command.
Returns the process stdout upon success if any.
On error print message the command with name that has failed. Print stdout
and stderr of the process if any, and then exit with given error code.
Args:
cmd (List[str]): Command in list format, each argument as their own item
name (str): Name of command to use when printing to command-line
errcode (int): Error code to use in case of exit()
cwd (str, optional): Folder to use as current work directory for command
execution. Defaults to ".".
capture_output (bool, optional): Whether to capture command output or not.
Defaults to True.
Returns:
str: stdout if any, or empty string
"""
cmd_process = subprocess.run(
cmd, capture_output=capture_output, encoding="UTF-8", cwd=cwd)
if cmd_process.returncode == 0:
if cmd_process.stdout:
return cmd_process.stdout
else:
return ""
else:
print(f"ERROR: {name} failed.")
if cmd_process.stdout:
print(cmd_process.stdout)
if cmd_process.stderr:
print(cmd_process.stderr)
exit(errcode)
return ""
def download_archives(base_url: str, archives: Callable[[str], Iterator[str]], version: str, dst_dir: pathlib.Path):
"""Download archives from the given base_url.
Archives is a generator for Blender archive names based on version.
Archive names are appended to the base_url to load from, and appended to
dst_dir to save to.
Args:
base_url (str): Base URL to load archives from
archives (Callable[[str], Iterator[str]]): Generator for Blender archive
names based on version
version (str): Version string in the form of 2.83.2
dst_dir (pathlib.Path): Download destination
"""
if base_url[-1] != '/':
base_url = base_url + '/'
for archive in archives(version):
download_url = f"{base_url}{archive}"
target_file = dst_dir.joinpath(archive)
download_file(download_url, target_file)
def download_file(from_url: str, to_file: pathlib.Path) -> None:
"""Download from_url as to_file.
Actual downloading will be skipped if --skipdl is given on the command-line.
Args:
from_url (str): Full URL to resource to download
to_file (pathlib.Path): Full path to save downloaded resource as
"""
global args
if not args.skipdl or not to_file.exists():
print(f"Downloading {from_url}")
with open(to_file, "wb") as download_zip:
response = requests.get(from_url)
if response.status_code != requests.codes.ok:
print(f"ERROR: failed to download {from_url} (status code: {response.status_code})")
exit(1313)
download_zip.write(response.content)
else:
print(f"Downloading {from_url} skipped")
print(" ... OK")
def copy_contents_from_dmg_to_path(dmg_file: pathlib.Path, dst: pathlib.Path) -> None:
"""Copy the contents of the given DMG file to the destination folder.
Args:
dmg_file (pathlib.Path): Full path to DMG archive to extract from
dst (pathlib.Path): Full path to destination to extract to
"""
hdiutil_attach = ["hdiutil",
"attach",
"-readonly",
f"{dmg_file}"
]
attached = execute_command(hdiutil_attach, "hdiutil attach", 1)
# Last line of output is what we want, it is of the form
# /dev/somedisk Apple_HFS /Volumes/Blender
# We want to retain the mount point, and the folder the mount is
# created on. The mounted disk we need for detaching, the folder we
# need to be able to copy the contents to where we can use them
attachment_items = attached.splitlines()[-1].split()
mounted_disk = attachment_items[0]
source_location = pathlib.Path(attachment_items[2], "Blender.app")
print(f"{source_location} -> {dst}")
shutil.copytree(source_location, dst)
hdiutil_detach = ["hdiutil",
"detach",
f"{mounted_disk}"
]
execute_command(hdiutil_detach, "hdiutil detach", 2)
def create_build_script(template_name: str, vars: List[Tuple[str, str]]) -> pathlib.Path:
"""
Create the Steam build script
Use the given template and template variable tuple list.
Returns pathlib.Path to the created script.
Args:
template_name (str): [description]
vars (List[Tuple[str, str]]): [description]
Returns:
pathlib.Path: Full path to the generated script
"""
build_script = pathlib.Path(".", template_name).read_text()
for var in vars:
build_script = build_script.replace(var[0], var[1])
build_script_file = template_name.replace(".template", "")
build_script_path = pathlib.Path(".", build_script_file)
build_script_path.write_text(build_script)
return build_script_path
def clean_up() -> None:
"""Remove intermediate files depending on given command-line arguments
"""
global content_location, args
if not args.leavearch and not args.leaveextracted:
shutil.rmtree(content_location)
if args.leavearch and not args.leaveextracted:
shutil.rmtree(content_location.joinpath(zip_extract_folder))
shutil.rmtree(content_location.joinpath(tarxz_extract_folder))
shutil.rmtree(content_location.joinpath(dmg_extract_folder))
if args.leaveextracted and not args.leavearch:
import os
os.remove(content_location.joinpath(zipped_blender))
os.remove(content_location.joinpath(tarxz_blender))
os.remove(content_location.joinpath(dmg_blender))
def extract_archive(archive: str, extract_folder_name: str,
cmd: List[str], errcode: int) -> None:
"""Extract all files from archive to given folder name.
Will not extract if
target folder already exists, or if --skipextract was given on the
command-line.
Args:
archive (str): Archive name to extract
extract_folder_name (str): Folder name to extract to
cmd (List[str]): Command with arguments to use
errcode (int): Error code to use for exit()
"""
global args, content_location
extract_location = content_location.joinpath(extract_folder_name)
pre_extract = set(content_location.glob("*"))
if not args.skipextract or not extract_location.exists():
print(f"Extracting files from {archive}...")
cmd.append(content_location.joinpath(archive))
execute_command(cmd, cmd[0], errcode, cwd=content_location)
# in case we use a non-release archive the naming will be incorrect.
# simply rename to expected target name
post_extract = set(content_location.glob("*"))
diff_extract = post_extract - pre_extract
if not extract_location in diff_extract:
folder_to_rename = list(diff_extract)[0]
folder_to_rename.rename(extract_location)
print(" OK")
else:
print(f"Skipping extraction {archive}!")
# ==============================================================================
parser = argparse.ArgumentParser()
parser.add_argument("--baseurl", required=True,
help="The base URL for files to download, "
"i.e. https://download.blender.org/release/Blender2.83/")
parser.add_argument("--version", required=True,
help="The Blender version to release, in the form 2.83.3")
parser.add_argument("--appid", required=True,
help="The Blender App ID on Steam")
parser.add_argument("--winid", required=True,
help="The Windows depot ID")
parser.add_argument("--linuxid", required=True,
help="The Linux depot ID")
parser.add_argument("--macosid", required=True,
help="The MacOS depot ID")
parser.add_argument("--steamcmd", required=True,
help="Path to the steamcmd")
parser.add_argument("--steamuser", required=True,
help="The login for the Steam builder user")
parser.add_argument("--steampw", required=True,
help="Login password for the Steam builder user")
add_optional_argument("--dryrun",
"If set the Steam files will not be uploaded")
add_optional_argument("--leavearch",
help="If set don't clean up the downloaded archives")
add_optional_argument("--leaveextracted",
help="If set don't clean up the extraction folders")
add_optional_argument("--skipdl",
help="If set downloading the archives is skipped if it already exists locally.")
add_optional_argument("--skipextract",
help="If set skips extracting of archives. The tool assumes the archives"
"have already been extracted to their correct locations")
args = parser.parse_args()
VERSIONNODOTS = args.version.replace('.', '')
OUTPUT = f"output{VERSIONNODOTS}"
CONTENT = f"content{VERSIONNODOTS}"
# ===== set up main locations
content_location = pathlib.Path(".", CONTENT).absolute()
output_location = pathlib.Path(".", OUTPUT).absolute()
content_location.mkdir(parents=True, exist_ok=True)
output_location.mkdir(parents=True, exist_ok=True)
# ===== login
# Logging into Steam once to ensure the SDK updates itself properly. If we don't
# do that the combined +login and +run_app_build_http at the end of the tool
# will fail.
steam_login = [args.steamcmd,
"+login",
args.steamuser,
args.steampw,
"+quit"
]
print("Logging in to Steam...")
execute_command(steam_login, "Login to Steam", 10)
print(" OK")
# ===== prepare Steam build scripts
template_vars = [
("[APPID]", args.appid),
("[OUTPUT]", OUTPUT),
("[CONTENT]", CONTENT),
("[VERSION]", args.version),
("[WINID]", args.winid),
("[LINUXID]", args.linuxid),
("[MACOSID]", args.macosid),
("[DRYRUN]", f"{args.dryrun}" if args.dryrun else "0")
]
blender_app_build = create_build_script(
"blender_app_build.vdf.template", template_vars)
create_build_script("depot_build_win.vdf.template", template_vars)
create_build_script("depot_build_linux.vdf.template", template_vars)
create_build_script("depot_build_macos.vdf.template", template_vars)
# ===== download archives
download_archives(args.baseurl, blender_archives,
args.version, content_location)
# ===== set up file and folder names
zipped_blender = get_archive_type("zip", args.version)
zip_extract_folder = zipped_blender.replace(".zip", "")
tarxz_blender = get_archive_type("tar.xz", args.version)
tarxz_extract_folder = tarxz_blender.replace(".tar.xz", "")
dmg_blender = get_archive_type("dmg", args.version)
dmg_extract_folder = dmg_blender.replace(".dmg", "")
# ===== extract
unzip_cmd = ["unzip", "-q"]
extract_archive(zipped_blender, zip_extract_folder, unzip_cmd, 3)
untarxz_cmd = ["tar", "-xf"]
extract_archive(tarxz_blender, tarxz_extract_folder, untarxz_cmd, 4)
if not args.skipextract or not content_location.joinpath(dmg_extract_folder).exists():
print("Extracting files from Blender MacOS archive...")
blender_dmg = content_location.joinpath(dmg_blender)
target_location = content_location.joinpath(
dmg_extract_folder, "Blender.app")
copy_contents_from_dmg_to_path(blender_dmg, target_location)
print(" OK")
else:
print("Skipping extraction of .dmg!")
# ===== building
print("Build Steam game files...")
steam_build = [args.steamcmd,
"+login",
args.steamuser,
args.steampw,
"+run_app_build_http",
blender_app_build.absolute(),
"+quit"
]
execute_command(steam_build, "Build with steamcmd", 13)
print(" OK")
clean_up()

View File

@@ -1,31 +0,0 @@
"DepotBuildConfig"
{
// Set your assigned depot ID here
"DepotID" "[LINUXID]"
// Set a root for all content.
// All relative paths specified below (LocalPath in FileMapping entries, and FileExclusion paths)
// will be resolved relative to this root.
// If you don't define ContentRoot, then it will be assumed to be
// the location of this script file, which probably isn't what you want
"ContentRoot" "./blender-[VERSION]-linux64/"
// include all files recursivley
"FileMapping"
{
// This can be a full path, or a path relative to ContentRoot
"LocalPath" "*"
// This is a path relative to the install folder of your game
"DepotPath" "."
// If LocalPath contains wildcards, setting this means that all
// matching files within subdirectories of LocalPath will also
// be included.
"recursive" "1"
}
// but exclude all symbol files
// This can be a full path, or a path relative to ContentRoot
"FileExclusion" "*.pdb"
}

View File

@@ -1,30 +0,0 @@
"DepotBuildConfig"
{
// Set your assigned depot ID here
"DepotID" "[MACOSID]"
// Set a root for all content.
// All relative paths specified below (LocalPath in FileMapping entries, and FileExclusion paths)
// will be resolved relative to this root.
// If you don't define ContentRoot, then it will be assumed to be
// the location of this script file, which probably isn't what you want
"ContentRoot" "./blender-[VERSION]-macOS/"
// include all files recursivley
"FileMapping"
{
// This can be a full path, or a path relative to ContentRoot
"LocalPath" "*"
// This is a path relative to the install folder of your game
"DepotPath" "."
// If LocalPath contains wildcards, setting this means that all
// matching files within subdirectories of LocalPath will also
// be included.
"recursive" "1"
}
// but exclude all symbol files
// This can be a full path, or a path relative to ContentRoot
"FileExclusion" "*.pdb"
}

View File

@@ -1,31 +0,0 @@
"DepotBuildConfig"
{
// Set your assigned depot ID here
"DepotID" "[WINID]"
// Set a root for all content.
// All relative paths specified below (LocalPath in FileMapping entries, and FileExclusion paths)
// will be resolved relative to this root.
// If you don't define ContentRoot, then it will be assumed to be
// the location of this script file, which probably isn't what you want
"ContentRoot" "./blender-[VERSION]-windows64/"
// include all files recursivley
"FileMapping"
{
// This can be a full path, or a path relative to ContentRoot
"LocalPath" "*"
// This is a path relative to the install folder of your game
"DepotPath" "."
// If LocalPath contains wildcards, setting this means that all
// matching files within subdirectories of LocalPath will also
// be included.
"recursive" "1"
}
// but exclude all symbol files
// This can be a full path, or a path relative to ContentRoot
"FileExclusion" "*.pdb"
}

View File

@@ -13,12 +13,6 @@
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
</application>
</compatibility>
<dependency>

View File

@@ -1,82 +1,4 @@
create_msix_package
===================
Buildbot Configuration
======================
This tool is used to create MSIX packages from a given ZiP archive. The MSIX
package is distributed mainly through the Microsoft Store. It can also be
installed when downloaded from blender.org. For that to work the MSIX package
needs to be signed.
Requirements
============
* MakeAppX.exe - this tool is distributed with the Windows 10 SDK and is used to build the .appx package.
* MakePri.exe - this tool is distributed with the Windows 10 SDK and is used to generate a resources file.
* SignTool.exe - this tool is distributed with the Windows 10 SDK and is used to sign the .appx package.
* Python 3 (3.7 or later tested) - to run the create_msix_package.py script
* requests module - can be installed with `pip install requests`
* PFX file (optional, but strongly recommended) - for signing the resulting MSIX
package. **NOTE:** If the MSIX package is not signed when uploaded to the Microsoft
store the validation and certification process can take up to three full
business day.
Usage
=====
On the command-line:
```batch
set VERSION=2.83.4.0
set URL=https://download.blender.org/release/Blender2.83/blender-2.83.4-windows64.zip
set PUBID=CN=PUBIDHERE
set PFX=X:\path\to\cert.pfx
set PFXPW=pwhere
python create_msix_package.py --version %VERSION% --url %URL% --publisher %PUBID% --pfx %PFX% --password %PFXPW%
```
Result will be a MSIX package with the name `blender-2.83.4-windows64.msix`.
With the above usage it will be signed. If the signing options are left out the
package will not be signed.
Optional arguments
==================
In support of testing and developing the manifest and scripts there are a few
optional arguments:
* `--skipdl` : If a `blender.zip` is available already next to the tool use this
to skip actual downloading of the archive designated by `--url`. The latter
option is still required
* `--overwrite` : When script fails the final clean-up may be incomplete leaving
the `Content` folder with its structure. Specify this argument to automatically
clean up this folder before starting to seed the `Content` folder
* `--leavezip` : When specified leave the `blender.zip` file while cleaning up
all other intermediate files, including the `Content` folder. This is useful
to not have to re-download the same archive from `--url` on each usage
What it does
============
The tool creates in the directory it lives a subfolder called `Content`. This is
where all necessary files are placed.
The `Assets` folder is copied to the `Content` folder.
From the application manifest template a version with necessary parts replaced as
their actual values as specified on the command-line is realized. This manifest controls the packaging of Blender into the MSIX format.
Next the tool downloads the designated ZIP archive locally as blender.zip. From
this archive the files are extracted into the `Content\Blender` folder, but skip
the leading part of paths in the ZIP. We want to write the files to the
content_blender_folder where blender.exe ends up as
`Content\Blender\blender.exe`, and not
`Content\Blender\blender-2.83.4-windows64\blender.exe`
Once the extraction is completed the MakeAppX tool is executed with the `Content`
folder as input. The result will be the MSIX package with the name in the form
`blender-X.YY.Z-windows64.msix`.
If the PFX file and its password are given on the command-line this MSIX package
will be signed.
All intermediate files and directories will be removed.
Files used by Buildbot's `package-code-store-windows` step.

View File

@@ -1,197 +0,0 @@
#!/usr/bin/env python3
import argparse
import os
import pathlib
import requests
import shutil
import subprocess
import zipfile
parser = argparse.ArgumentParser()
parser.add_argument(
"--version",
required=True,
help="Version string in the form of 2.83.3.0",
)
parser.add_argument(
"--url",
required=True,
help="Location of the release ZIP archive to download",
)
parser.add_argument(
"--publisher",
required=True,
help="A string in the form of 'CN=PUBLISHER'",
)
parser.add_argument(
"--pfx",
required=False,
help="Absolute path to the PFX file used for signing the resulting MSIX package",
)
parser.add_argument(
"--password",
required=False,
default="blender",
help="Password for the PFX file",
)
parser.add_argument(
"--lts",
required=False,
help="If set this MSIX is for an LTS release",
action='store_const',
const=1,
)
parser.add_argument(
"--skipdl",
required=False,
help="If set skip downloading of the specified URL as blender.zip. The tool assumes blender.zip exists",
action='store_const',
const=1,
)
parser.add_argument(
"--leavezip",
required=False,
help="If set don't clean up the downloaded blender.zip",
action='store_const',
const=1,
)
parser.add_argument(
"--overwrite",
required=False,
help="If set remove Content folder if it already exists",
action='store_const',
const=1,
)
args = parser.parse_args()
def execute_command(cmd: list, name: str, errcode: int):
"""
Execute given command in cmd. Output is captured. If an error
occurs name is used to print ERROR message, along with stderr and
stdout of the process if either was captured.
"""
cmd_process = subprocess.run(cmd, capture_output=True, encoding="UTF-8")
if cmd_process.returncode != 0:
print(f"ERROR: {name} failed.")
if cmd_process.stdout:
print(cmd_process.stdout)
if cmd_process.stderr:
print(cmd_process.stderr)
exit(errcode)
LTSORNOT = ""
PACKAGETYPE = ""
if args.lts:
versionparts = args.version.split(".")
LTSORNOT = f" {versionparts[0]}.{versionparts[1]} LTS"
PACKAGETYPE = f"{versionparts[0]}.{versionparts[1]}LTS"
blender_package_msix = pathlib.Path(".", f"blender-{args.version}-windows64.msix").absolute()
content_folder = pathlib.Path(".", "Content")
content_blender_folder = pathlib.Path(content_folder, "Blender").absolute()
content_assets_folder = pathlib.Path(content_folder, "Assets")
assets_original_folder = pathlib.Path(".", "Assets")
pri_config_file = pathlib.Path(".", "priconfig.xml")
pri_resources_file = pathlib.Path(content_folder, "resources.pri")
local_blender_zip = pathlib.Path(".", "blender.zip")
if args.pfx:
pfx_path = pathlib.Path(args.pfx)
if not pfx_path.exists():
print("ERROR: PFX file not found. Please ensure you give the correct path to the PFX file on the command-line.")
exit(1)
print(f"Creating MSIX package with signing using PFX file at {pfx_path}")
else:
pfx_path = None
print("Creating MSIX package without signing.")
pri_command = ["makepri",
"new",
"/pr", f"{content_folder.absolute()}",
"/cf", f"{pri_config_file.absolute()}",
"/of", f"{pri_resources_file.absolute()}"
]
msix_command = ["makeappx",
"pack",
"/h", "SHA256",
"/d", f"{content_folder.absolute()}",
"/p", f"{blender_package_msix}"
]
if pfx_path:
sign_command = ["signtool",
"sign",
"/fd", "sha256",
"/a", "/f", f"{pfx_path.absolute()}",
"/p", f"{args.password}",
f"{blender_package_msix}"
]
if args.overwrite:
if content_folder.joinpath("Assets").exists():
shutil.rmtree(content_folder)
content_folder.mkdir(exist_ok=True)
shutil.copytree(assets_original_folder, content_assets_folder)
manifest_text = pathlib.Path("AppxManifest.xml.template").read_text()
manifest_text = manifest_text.replace("[VERSION]", args.version)
manifest_text = manifest_text.replace("[PUBLISHER]", args.publisher)
manifest_text = manifest_text.replace("[LTSORNOT]", LTSORNOT)
manifest_text = manifest_text.replace("[PACKAGETYPE]", PACKAGETYPE)
pathlib.Path(content_folder, "AppxManifest.xml").write_text(manifest_text)
if not args.skipdl:
print(f"Downloading blender archive {args.url} to {local_blender_zip}...")
with open(local_blender_zip, "wb") as download_zip:
response = requests.get(args.url)
download_zip.write(response.content)
print("... download complete.")
else:
print("Skipping download")
print(f"Extracting files from ZIP to {content_blender_folder}...")
# Extract the files from the ZIP archive, but skip the leading part of paths
# in the ZIP. We want to write the files to the content_blender_folder where
# blender.exe ends up as ./Content/Blender/blender.exe, and not
# ./Content/Blender/blender-2.83.3-windows64/blender.exe
with zipfile.ZipFile(local_blender_zip, "r") as blender_zip:
for entry in blender_zip.infolist():
if entry.is_dir():
continue
entry_location = pathlib.Path(entry.filename)
target_location = content_blender_folder.joinpath(*entry_location.parts[1:])
pathlib.Path(target_location.parent).mkdir(parents=True, exist_ok=True)
extracted_entry = blender_zip.read(entry)
target_location.write_bytes(extracted_entry)
print("... extraction complete.")
print(f"Generating Package Resource Index (PRI) file using command: {' '.join(pri_command)}")
execute_command(pri_command, "MakePri", 4)
print(f"Creating MSIX package using command: {' '.join(msix_command)}")
# Remove MSIX file if it already exists. Otherwise the MakeAppX tool
# will hang.
if blender_package_msix.exists():
os.remove(blender_package_msix)
execute_command(msix_command, "MakeAppX", 2)
if args.pfx:
print(f"Signing MSIX package using command: {' '.join(sign_command)}")
execute_command(sign_command, "SignTool", 3)
if not args.leavezip:
os.remove(local_blender_zip)
shutil.rmtree(content_folder)
print("Done.")

View File

@@ -66,7 +66,7 @@ static unsigned long ft_ansi_stream_io(FT_Stream stream,
file = STREAM_FILE(stream);
if (stream->pos != offset) {
fseek(file, offset, SEEK_SET);
BLI_fseek(file, offset, SEEK_SET);
}
return fread(buffer, 1, count, file);
@@ -93,7 +93,7 @@ static FT_Error FT_Stream_Open__win32_compat(FT_Stream stream, const char *filep
return FT_THROW(Cannot_Open_Resource);
}
fseek(file, 0, SEEK_END);
BLI_fseek(file, 0LL, SEEK_END);
stream->size = ftell(file);
if (!stream->size) {
fprintf(stderr,
@@ -104,7 +104,7 @@ static FT_Error FT_Stream_Open__win32_compat(FT_Stream stream, const char *filep
return FT_THROW(Cannot_Open_Stream);
}
fseek(file, 0, SEEK_SET);
BLI_fseek(file, 0LL, SEEK_SET);
stream->descriptor.pointer = file;
stream->read = ft_ansi_stream_io;

View File

@@ -52,7 +52,7 @@ inline void convert_to_static_type(const CustomDataType data_type, const Func &f
func(bool());
break;
case CD_PROP_COLOR:
func(Color4f());
func(ColorGeometry4f());
break;
default:
BLI_assert_unreachable();
@@ -78,8 +78,8 @@ inline void convert_to_static_type(const fn::CPPType &cpp_type, const Func &func
else if (cpp_type.is<bool>()) {
func(bool());
}
else if (cpp_type.is<Color4f>()) {
func(Color4f());
else if (cpp_type.is<ColorGeometry4f>()) {
func(ColorGeometry4f());
}
else {
BLI_assert_unreachable();
@@ -123,9 +123,12 @@ inline float3 mix3(const float3 &weights, const float3 &v0, const float3 &v1, co
}
template<>
inline Color4f mix3(const float3 &weights, const Color4f &v0, const Color4f &v1, const Color4f &v2)
inline ColorGeometry4f mix3(const float3 &weights,
const ColorGeometry4f &v0,
const ColorGeometry4f &v1,
const ColorGeometry4f &v2)
{
Color4f result;
ColorGeometry4f result;
interp_v4_v4v4v4(result, v0, v1, v2, weights);
return result;
}
@@ -165,9 +168,10 @@ template<> inline float3 mix2(const float factor, const float3 &a, const float3
return float3::interpolate(a, b, factor);
}
template<> inline Color4f mix2(const float factor, const Color4f &a, const Color4f &b)
template<>
inline ColorGeometry4f mix2(const float factor, const ColorGeometry4f &a, const ColorGeometry4f &b)
{
Color4f result;
ColorGeometry4f result;
interp_v4_v4v4(result, a, b, factor);
return result;
}
@@ -274,15 +278,16 @@ class SimpleMixerWithAccumulationType {
}
};
class Color4fMixer {
class ColorGeometryMixer {
private:
MutableSpan<Color4f> buffer_;
Color4f default_color_;
MutableSpan<ColorGeometry4f> buffer_;
ColorGeometry4f default_color_;
Array<float> total_weights_;
public:
Color4fMixer(MutableSpan<Color4f> buffer, Color4f default_color = {0, 0, 0, 1});
void mix_in(const int64_t index, const Color4f &color, const float weight = 1.0f);
ColorGeometryMixer(MutableSpan<ColorGeometry4f> buffer,
ColorGeometry4f default_color = ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f));
void mix_in(const int64_t index, const ColorGeometry4f &color, const float weight = 1.0f);
void finalize();
};
@@ -299,10 +304,10 @@ template<> struct DefaultMixerStruct<float2> {
template<> struct DefaultMixerStruct<float3> {
using type = SimpleMixer<float3>;
};
template<> struct DefaultMixerStruct<Color4f> {
/* Use a special mixer for colors. Color4f can't be added/multiplied, because this is not
template<> struct DefaultMixerStruct<ColorGeometry4f> {
/* Use a special mixer for colors. ColorGeometry4f can't be added/multiplied, because this is not
* something one should usually do with colors. */
using type = Color4fMixer;
using type = ColorGeometryMixer;
};
template<> struct DefaultMixerStruct<int> {
static int double_to_int(const double &value)

View File

@@ -39,7 +39,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 1
#define BLENDER_FILE_SUBVERSION 2
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@@ -30,11 +30,60 @@ struct Main;
struct PointerRNA;
/**
* Common suffix uses:
* - ``_PRE/_POST``:
* For handling discrete non-interactive events.
* - ``_INIT/_COMPLETE/_CANCEL``:
* For handling jobs (which may in turn cause other handlers to be called).
Callbacks for One Off Actions
* =============================
*
* - `{ACTION}` use in cases where only a single callback is required,
* `VERSION_UPDATE` and `RENDER_STATS` for example.
*
* \note avoid single callbacks if there is a chance `PRE/POST` are useful to differentiate
* since renaming callbacks may break Python scripts.
*
* Callbacks for Common Actions
* ============================
*
* - `{ACTION}_PRE` run before the action.
* - `{ACTION}_POST` run after the action.
*
* Optional Additional Callbacks
* -----------------------------
*
* - `{ACTION}_INIT` when the handler may manipulate the context used to run the action.
*
* Examples where `INIT` functions may be useful are:
*
* - When rendering, an `INIT` function may change the camera or render settings,
* things which a `PRE` function can't support as this information has already been used.
* - When saving an `INIT` function could temporarily change the preferences.
*
* - `{ACTION}_POST_FAIL` should be included if the action may fail.
*
* Use this so a call to the `PRE` callback always has a matching call to `POST` or `POST_FAIL`.
*
* \note in most cases only `PRE/POST` are required.
*
* Callbacks for Background/Modal Tasks
* ====================================
*
* - `{ACTION}_INIT`
* - `{ACTION}_COMPLETE` when a background job has finished.
* - `{ACTION}_CANCEL` When a background job is canceled partway through.
*
* While cancellation may be caused by any number of reasons, common causes may include:
*
* - Explicit user cancellation.
* - Exiting Blender.
* - Failure to acquire resources (such as disk-full, out of memory ... etc).
*
* \note `PRE/POST` handlers may be used along side modal task handlers
* as is the case for rendering, where rendering an animation uses modal task handlers,
* rendering a single frame has `PRE/POST` handlers.
*
* Python Access
* =============
*
* All callbacks here must be exposed via the Python module `bpy.app.handlers`,
* see `bpy_app_handlers.c`.
*/
typedef enum {
BKE_CB_EVT_FRAME_CHANGE_PRE,

View File

@@ -39,7 +39,8 @@ struct Mesh;
struct Object;
struct PointCloud;
struct Volume;
class CurveEval;
struct Curve;
struct CurveEval;
enum class GeometryOwnershipType {
/* The geometry is owned. This implies that it can be changed. */
@@ -406,6 +407,15 @@ class CurveComponent : public GeometryComponent {
CurveEval *curve_ = nullptr;
GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
/**
* Curve data necessary to hold the draw cache for rendering, consistent over multiple redraws.
* This is necessary because Blender assumes that objects evaluate to an object data type, and
* we use #CurveEval rather than #Curve here. It also allows us to mostly reuse the same
* batch cache implementation.
*/
mutable Curve *curve_for_render_ = nullptr;
mutable std::mutex curve_for_render_mutex_;
public:
CurveComponent();
~CurveComponent();
@@ -420,12 +430,18 @@ class CurveComponent : public GeometryComponent {
CurveEval *get_for_write();
int attribute_domain_size(const AttributeDomain domain) const final;
std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain(
std::unique_ptr<blender::fn::GVArray> varray,
const AttributeDomain from_domain,
const AttributeDomain to_domain) const final;
bool is_empty() const final;
bool owns_direct_data() const override;
void ensure_owns_direct_data() override;
const Curve *get_curve_for_render() const;
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE;
private:

View File

@@ -258,8 +258,10 @@ void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b);
void id_sort_by_name(struct ListBase *lb, struct ID *id, struct ID *id_sorting_hint);
void BKE_lib_id_expand_local(struct Main *bmain, struct ID *id);
bool BKE_id_new_name_validate(struct ListBase *lb, struct ID *id, const char *name)
ATTR_NONNULL(1, 2);
bool BKE_id_new_name_validate(struct ListBase *lb,
struct ID *id,
const char *name,
const bool do_linked_data) ATTR_NONNULL(1, 2);
void BKE_lib_id_clear_library_data(struct Main *bmain, struct ID *id);
/* Affect whole Main database. */
@@ -273,7 +275,7 @@ void BKE_main_id_tag_all(struct Main *mainvar, const int tag, const bool value);
void BKE_main_id_flag_listbase(struct ListBase *lb, const int flag, const bool value);
void BKE_main_id_flag_all(struct Main *bmain, const int flag, const bool value);
void BKE_main_id_clear_newpoins(struct Main *bmain);
void BKE_main_id_newptr_and_tag_clear(struct Main *bmain);
void BKE_main_id_refcount_recompute(struct Main *bmain, const bool do_linked_only);

View File

@@ -47,6 +47,7 @@ struct ID;
struct IDOverrideLibrary;
struct IDOverrideLibraryProperty;
struct IDOverrideLibraryPropertyOperation;
struct Library;
struct Main;
struct Object;
struct PointerRNA;
@@ -68,7 +69,9 @@ bool BKE_lib_override_library_is_user_edited(struct ID *id);
struct ID *BKE_lib_override_library_create_from_id(struct Main *bmain,
struct ID *reference_id,
const bool do_tagged_remap);
bool BKE_lib_override_library_create_from_tag(struct Main *bmain);
bool BKE_lib_override_library_create_from_tag(struct Main *bmain,
const struct Library *reference_library,
const bool do_no_main);
bool BKE_lib_override_library_create(struct Main *bmain,
struct Scene *scene,
struct ViewLayer *view_layer,

View File

@@ -85,6 +85,10 @@ enum {
* freed ones).
*/
ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS = 1 << 7,
/** Force handling user count even for IDs that are outside of Main (used in some cases when
* dealing with IDs temporarily out of Main, but which will be put in it ultimately).
*/
ID_REMAP_FORCE_USER_REFCOUNT = 1 << 8,
};
/* Note: Requiring new_id to be non-null, this *may* not be the case ultimately,

View File

@@ -113,6 +113,7 @@ void BKE_id_material_clear(struct Main *bmain, struct ID *id);
struct Material *BKE_object_material_get_eval(struct Object *ob, short act);
int BKE_object_material_count_eval(struct Object *ob);
void BKE_id_material_eval_assign(struct ID *id, int slot, struct Material *material);
void BKE_id_material_eval_ensure_default_slot(struct ID *id);
/* rendering */

Some files were not shown because too many files have changed in this diff Show More