Compare commits

...

79 Commits
v1.13 ... main

Author SHA1 Message Date
4085f0015f rollback zip changes 2026-02-20 11:00:12 +01:00
cf31dbc5ec Update README.md 2026-02-20 10:51:45 +01:00
bb1de717c8 Refactor Blender addon structure and add explicit --zip CLI flag
Move operators and preferences out of __init__.py into dedicated modules.
Fix cyclic import by using proper AddonPreferences pattern. Replace
implicit .zip extension detection in CLI with explicit -z/--zip flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:50:31 +01:00
12e6e2643b zip operator 2026-02-19 18:06:34 +01:00
cbbe8be0d8 keep hierarchy option 2026-02-19 18:06:25 +01:00
Sybren A. Stüvel
6af9567a44 Mark 1.21 as released today 2025-11-24 16:42:57 +01:00
Sybren A. Stüvel
74860b3107 Bumped version to 1.21 2025-11-24 16:39:34 +01:00
Sybren A. Stüvel
5c0edf4c07 Test Python 3.14 with Tox 2025-11-24 16:02:05 +01:00
Sybren A. Stüvel
a973637896 Upgrade some dependencies
Upgrade `zstandard` to `0.16`, because that is what's used in the oldest
still-supported Blender (4.2-LTS).

This also upgrades Pytest to 7.4.4 and Pytest-cov to 4.1.0. Still a
conservative upgrade, but at least it gets rid of various deprecation
warnings.
2025-11-24 16:02:05 +01:00
Sybren A. Stüvel
7322c0772a Poetry: Bump Python to 3.11 and update build system to poetry-core
The oldest still-supported version of Blender is 4.2-LTS, which uses
Python 3.11, so that's what's now the required Python version for BAT.

Make the build system depend on `poetry-core` instead of `poetry`, to
make package builds faster (especially important when using Tox for
testing in various environments).

This also re-generates the `poetry.lock` file, so updates the
dependencies to their latest version compatible with the `project.toml`
file.
2025-11-24 16:02:05 +01:00
Sybren A. Stüvel
3b61399f7f Update CHANGELOG.md with recent commits 2025-11-24 15:34:45 +01:00
Sybren A. Stüvel
aceea0d823 Skip tracing packed blend files
BAT will assume that the packed file is self-contained, i.e. any asset
used by a packed blend file should also be packed.
2025-11-24 15:30:03 +01:00
Andrej730
2c0fe87fba cli.blocks - print biggest block's address as hex for readibility (#92900)
As addresses typically represented everywhere as hex values.

Before:
```
Biggest ARegion-DATA block is 304 B at address 1585888006560
Finding what points there
     <BlendFileBlock.ScrArea (DATA), size=184 at 0x1713e4acd60> (b'regionbase', b'first')
     <BlendFileBlock.ARegion (DATA), size=304 at 0x1713e4a9020> b'prev'
```

After:
```
Biggest ARegion-DATA block is 304 B at address 0x1713e4a91a0
Finding what points there
     <BlendFileBlock.ScrArea (DATA), size=184 at 0x1713e4acd60> (b'regionbase', b'first')
     <BlendFileBlock.ARegion (DATA), size=304 at 0x1713e4a9020> b'prev'
```

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92900
2025-11-24 15:08:39 +01:00
Andrej730
47c3e8d77f Struct.field_set - support setting subproperties (#92899)
Extend `Struct.field_set()` so that the `path` type can be a
`dna.FieldPath`, to mirror `Struct.field_get`'s `path` type, and allow
users to set file-block's subproperties. For example this allows
setting `object_block[(b'id', 'name')]`.

Also, along the way, added a test for getting subproperty value.

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92899
2025-11-24 15:07:46 +01:00
Andrej730
c69612b264 BlendFileBlock.get to support accessing different items in the file-block (#92898)
Add an `array_index` parameter to `block.get(property_name)` to get a
specific item from an array.

Example:
```python
verts = self.bf.block_from_addr[verts_ptr]
assert verts.get(b"co") == [-1.0, -1.0, -1.0]  # index 0
assert verts.get(b"co", array_index=1) == [-1.0, -1.0, 1.0]
```

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92898
2025-11-24 14:53:15 +01:00
Andrej730
b1c4f5e116 setup.cfg - set enabled pytest_cov explicitly (#92896)
To support running tests if `PYTEST_DISABLE_PLUGIN_AUTOLOAD` is set
(to avoid conflicts with other plugins that might be available on the
system).

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92896
2025-11-24 14:40:35 +01:00
Andrej730
5690c8e7cb Add py.typed marker file (#92895)
PEP 561 added a requirement for packages that do have typing
information, to include empty py.typed file:
https://peps.python.org/pep-0561/#packaging-type-information

Example warnings from pyright/pylance:
```python
# Stub file not found for "blender_asset_tracer" Pylance: reportMissingTypeStubs
from blender_asset_tracer import blendfile
```

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92895
2025-11-24 14:38:15 +01:00
Jonas Dichelle
50578ac62a Add Support for Geometry Node Cache (#92890)
Add support for geometry node simulation cache files.

This also adds support for dealing with dynamic arrays in Blender's
DNA, because `modifier.bakes` is a pointer to such an array.

Co-authored-by: Sybren A. Stüvel <sybren@blender.org>
Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92890
2025-11-24 13:06:38 +01:00
Andrej730
94486a3218 Add additional annotations to avoid typing issues (#92897)
BlendFileBlock attributes:

Explicit annotation on BlendFileBlock are needed because otherwise
e.g. `block.add_old` type was imprecisely inferred from the
assignments as `int | Any`, where `Any` comes from `.unpack` returning
`tuple[Any, ...]`.

Ideally unpack should be somehow connected to the returned types, but
this solution should work for now just to avoid typing errors.

dna_io - add some missing annotations:

Some annotations were needed to ensure `block.code` will be inferred
as `bytes` and not `bytes | Unknown`.

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92897
Reviewed-by: Sybren A. Stüvel <sybren@blender.org>
2025-08-25 11:51:25 +02:00
Andrej730
95165a0330 README - code example to use Python syntax highlighting (#92894)
Added explicit code block  for Python syntax highlighting.

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92894
2025-08-25 11:51:25 +02:00
Sybren A. Stüvel
8a17495566 Bump version to 1.20 and mark it as released today 2025-08-25 11:51:25 +02:00
Sybren A. Stüvel
08b37a35f8 Update CHANGELOG.md 2025-07-11 15:45:24 +02:00
Sybren A. Stüvel
6c42d06f05 Add a __main__.py file
Add a `__main__.py` file, so that BAT can be run with
`python3 -m blender_asset_tracer`. This makes it easier to start BAt in
a debugger.
2025-07-11 15:42:39 +02:00
Sybren A. Stüvel
16c208bc8e Support compositor node trees in Blender 5.0
Since Blender 5.0 these use a different DNA field. BAT now supports
this too, and supports linked node trees as well.

See blender/blender@bd61e69be5
2025-07-11 15:42:07 +02:00
Sybren A. Stüvel
4c429e9212 Read blendfile sub-version
Add support for reading the blend file's sub-version.
2025-07-11 15:30:58 +02:00
Sybren A. Stüvel
2489e4cbbc Upgrade Twine 4.0 → 5.0
There was an error running `twine check` (`KeyError: 'license'`) that was
fixed by upgrading to 5.0. This version was chosen as it's the one in
the current Ubuntu LTS release.
2025-06-16 12:38:13 +02:00
Sybren A. Stüvel
9223a4d7b9 Bumped version to 1.19 2025-06-16 12:29:45 +02:00
Sybren A. Stüvel
896c4a3ca4 README: update development environment setup instructions 2025-06-16 11:59:51 +02:00
Sybren A. Stüvel
85cf257d2c Upgrade Sphinx to the version bundled with Ubuntu 24.04 LTS
The old version of Sphinx depended on the `pathtools` library, which had
issues installing on my macbook.
2025-06-16 11:52:52 +02:00
Sybren A. Stüvel
03fa3f2d18 Drop support for Python 3.8
It is EOL and no longer supported by the Python foundation.

Also it is getting in the way of upgrading Sphinx.
2025-06-16 11:51:09 +02:00
Sybren A. Stüvel
b1d49627b1 Add Python 3.12 and 3.13 to the Tox environments 2025-06-16 11:41:00 +02:00
Sybren A. Stüvel
876dddb964 CHANGELOG: update log for 1.19 and mark it as released today 2025-06-16 11:26:00 +02:00
Sybren A. Stüvel
12ce4bfc6d pyproject.toml: modernize use of deprecated tool.poetry.dev-dependencies
Use `[tool.poetry.group.dev.dependencies]` instead of the deprecated
`[tool.poetry.dev-dependencies]` section.
2025-06-16 11:24:13 +02:00
Jacques Lucke
f1ee7980b2 Support .blend files saved with large bhead
This is mostly the same as blender/blender!140195. The header parsing code has
been updated to be able to read old and new .blend file headers.

There is a new test file which is the same as the existing `basic_file.blend`,
but saved with the new header format. A new unit test has been added to check
that this file is read correctly as well.

Pull Request: https://projects.blender.org/blender/blender-asset-tracer/pulls/92893
2025-06-13 12:25:51 +02:00
Sybren A. Stüvel
eb69ca5632 Add EndianIO.parse_pointer function
This is to parse in-memory pointer data bytes into an actual pointer value.
2025-01-24 15:34:46 +01:00
Sybren A. Stüvel
f2e28edc05 Implement reader & writer for int8_t type 2024-09-02 18:20:51 +02:00
Sybren A. Stüvel
e3d3d988b7 Re-lock package dependencies
Re-ran `poetry lock` to regenerate the `poetry.lock` file.
2024-09-02 18:20:51 +02:00
Sybren A. Stüvel
073bc8140a Update CHANGELOG 2024-09-02 18:20:51 +02:00
jonasdichelle
16a092ddf1 Add Support for Dynamic Paint Modifier
Add support for the Dynamic Paint modifier point cache.

I added a walker to iterate through all surfaces on a canvas to get each surface's point cache.

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92889
Reviewed-by: Sybren A. Stüvel <sybren@blender.org>
2024-09-02 18:19:07 +02:00
Bastien Montagne
fe0b3e8f5e Add a simple, direct way to use BAT, without requiring to setup a venv etc.
Allows to use `python3 path/to/cli.py my args` just from source code,
without requiring to set up a whole venv for that. Much simpler when
only 'regular' debuging in code logic itself is needed.
2024-08-13 18:01:38 +02:00
Sybren A. Stüvel
42dd5ec1b6 Bumped version to 1.18 2024-01-11 16:43:11 +01:00
Sybren A. Stüvel
77778f1895 Mark v1.18 as released today 2024-01-11 16:39:27 +01:00
Olivier Charvin
44d1d2ff90 Fix IES lights asset tracing
Fix an issue where BAT failed with a `KeyError` (because it couldn't
find the `filepath` property) when handling IES light files.

Also add example files & unit tests.

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92886
2024-01-11 16:34:28 +01:00
Sybren A. Stüvel
15f00e6c92 Add support for reading uchar aka uint8_t 2024-01-08 17:08:13 +01:00
Olivier Charvin
ced4af98a3 Add support for OpenVDB
Add support for tracing `VO` data-blocks, for OpenVDB volumes.
2024-01-02 16:32:04 +01:00
Sybren A. Stüvel
937ecfdece Update CHANGELOG 2023-12-18 17:59:37 +01:00
Sybren A. Stüvel
98892151ed Bumped version to 1.18-dev0 2023-12-18 17:58:30 +01:00
Olivier Charvin
2099827554 blocks2assets: debug filepath on reading unsupported blocks (#92885)
When logging that there is no reader implemented for a certain
data-block type, include the filepath of the blend file that contains
that data-block.

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92885
2023-12-18 17:57:42 +01:00
Sybren A. Stüvel
47e7c00845 Bumped version to 1.17 2023-12-14 11:54:30 +01:00
Sybren A. Stüvel
1abb8e5650 Mark 1.17 as released today 2023-12-14 11:54:21 +01:00
Sybren A. Stüvel
61dc5b8a23 Fix issue packing lamps with non-IES 'storage'
Lamp/light nodes might have a `storage` property that might point to an
IES texture node (which has the expected `filepath`), but not always.
The latter now no longer crashes BAT.

This fixes https://projects.blender.org/blender/blender-asset-tracer/pulls/92883
2023-12-14 11:36:55 +01:00
Sybren A. Stüvel
81ea8296da Bump version to 1.17-dev0 2023-12-14 11:36:34 +01:00
Sybren A. Stüvel
2a9bdc5206 Mark 1.16 as released today 2023-11-02 15:47:35 +01:00
Sybren A. Stüvel
c4ff3fdb54 Bumped version to 1.16 2023-11-02 15:46:01 +01:00
Sybren A. Stüvel
29c0ba14b1 CHANGELOG: add Python version changes
Document drop of support for 3.7.

Also I forgot to mention that v1.15 added support for Python 3.11.
2023-11-02 15:07:17 +01:00
Sybren A. Stüvel
e2b2e6fb68 Drop support for Python 3.7
Python 3.7 is EOL, no longer supported.
2023-11-02 15:05:31 +01:00
Sybren A. Stüvel
36c258985e Cleanup: formatting change of pyproject.toml
No functional changes.
2023-11-02 15:05:16 +01:00
Sybren A. Stüvel
ef5b83feda CHANGELOG: update for new tracing behaviour of directory assets 2023-11-02 15:01:05 +01:00
Sybren A. Stüvel
606377180c Pack: always pack files, explode directories to a list of files
When an asset is represented as directory in Blender (for example fluid
simulation caches), that directory is traced and each file is considered
an asset.

This makes it considerably easier for Shaman clients, as they need to
compute the SHA256 checksum of each file. The logic to transform a
directory path to a list of the contained files is now in BAT itself.
2023-11-02 14:53:52 +01:00
Sybren A. Stüvel
f9bc6f2d08 Cleanup: fix mypy errors
This mostly handles a mypy change where implicit optionals (i.e. things
like `size: int = None`) are no longer allowed.

No functional changes.
2023-11-02 14:46:38 +01:00
Sybren A. Stüvel
b3fb7845c3 Cleanup: Reformat with black
No functional changes.
2023-11-02 14:40:27 +01:00
Olivier Charvin
74b3df5f99 Add support for IES lights
Add support for tracing `.ies` files referenced by lights.

Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92883
2023-10-16 12:14:07 +02:00
Sybren A. Stüvel
055457ab67 Add BlendFileBlock.raw_data() and .as_string() functions
Add functions to interpret the data in a `BlendFileBlock` as either `bytes`
or `string`. This is used to obtain the contents of a `char*` (instead
of an embedded `char[N]` array).
2023-05-16 16:01:38 +02:00
Sybren A. Stüvel
d2b353d1bf README: fix copy-paste error 2022-12-16 15:26:26 +01:00
Sybren A. Stüvel
9641ea1b58 Adjust filenames for newer Poetry
Newer Poetry versions seem to write sources to
`blender_asset_tracer-1.15.tar.gz`, i.e. with underscores in the name,
just like the wheel file.
2022-12-16 15:25:19 +01:00
Sybren A. Stüvel
64d657ead2 Poetry: include a readme setting
This should include the contents of `README.md` in the package & show it
on the Python package index.
2022-12-16 15:22:27 +01:00
Sybren A. Stüvel
54456e0c35 Also test on Python 3.11 2022-12-16 15:14:09 +01:00
Sybren A. Stüvel
e6669887ce Update all dependencies to their latest versions 2022-12-16 15:08:59 +01:00
Sybren A. Stüvel
aba2b673f1 Change my email address
Change the email address to my work address.
2022-12-16 15:01:38 +01:00
Sybren A. Stüvel
ce19808f5c Bumped version to 1.15 2022-12-16 15:00:43 +01:00
Sybren A. Stüvel
8f933ef862 Mark 1.15 as released today 2022-12-16 15:00:31 +01:00
Sybren A. Stüvel
517e8eee23 Bumped version to 1.15-dev0 2022-12-16 14:49:56 +01:00
Sybren A. Stüvel
1e8c924990 Add support for fluid simulation caches
Add `eModifierType_Fluid` support, with non-pointcache caches. The entire
cache directory is considered a dependency to list/pack.
2022-12-16 14:49:36 +01:00
Sybren A. Stüvel
1541c52520 Version bump to 1.14
This was actually released in 2022-09-12, but I forgot to commit!
2022-12-02 13:11:37 +01:00
Sybren A. Stüvel
79193a60cd CHANGELOG: mark 1.14 as released today 2022-09-12 15:28:04 +02:00
Sybren A. Stüvel
330b179a44 README: add new instructions on building & publishing
PyPi requires an API token nowadays, for uploading packages. The README
now includes instructions on how to set those up & use them.
2022-09-12 15:28:04 +02:00
Sybren A. Stüvel
3bcb9bab0e Publish packages with Twine
Twine is the preferred way to publish Python packages nowadays.
2022-09-12 15:27:51 +02:00
Sybren A. Stüvel
051cab877c Poetry: update dependencies 2022-09-12 15:06:08 +02:00
Sybren A. Stüvel
cc4043969e Change when the trace_blendfile() callback is called
Previously the `callback.trace_blendfile()` callback was called just before
tracing its dependencies would start, i.e. after opening it. This is now
changed to before opening it, because that can take a long time (to load
SDNA). this will make any UI (like the Flamenco add-on) report the right
filename when waiting for big files, instead of lingering on the
last-opened (potentially very small) blend file.
2022-09-12 15:06:08 +02:00
85 changed files with 4939 additions and 1148 deletions

View File

@ -3,6 +3,57 @@
This file logs the changes that are actually interesting to users (new features, This file logs the changes that are actually interesting to users (new features,
changed functionality, fixed bugs). changed functionality, fixed bugs).
# Version 1.21 (2025-11-24)
- Require Python version 3.11 or newer. Versions up to Python 3.14 are supported.
- Skip packed blend files. BAT will assume that the packed file is self-contained, i.e. any asset used by a packed blend file should also be packed.
- Support Geometry Node simulation caches ([#92890](https://projects.blender.org/blender/blender-asset-tracer/pulls/92890)).
- Add a `py.typed` marker file, such that tooling like mypy know BAT has type annotations ([#92895](https://projects.blender.org/blender/blender-asset-tracer/pulls/92895)).
- Add support for getting individual array items ([#92898](https://projects.blender.org/blender/blender-asset-tracer/pulls/92898)) and iterating dynamic arrays (part of [#92890](https://projects.blender.org/blender/blender-asset-tracer/pulls/92890)).
- Support setting sub-properties via `block.field_set()` ([#92899](https://projects.blender.org/blender/blender-asset-tracer/pulls/92899)).
- Make `bat blocks` print the biggest block memory address as hexadecimal ([#92900](https://projects.blender.org/blender/blender-asset-tracer/pulls/92900)).
# Version 1.20 (2025-07-11)
- Add support for Blender 5.0 compositor node trees ([16c208bc8e13](https://projects.blender.org/blender/blender-asset-tracer/commit/16c208bc8e130c8b1233bdb411ecabdab19af3c5)).
- Make it possible to run BAT with `python -m blender_asset_tracer` ([6c42d06f0590](https://projects.blender.org/blender/blender-asset-tracer/commit/6c42d06f05909d4ac2096e84557d19dd93382f3a)).
- Add support for loading the file sub-version ([4c429e921228](https://projects.blender.org/blender/blender-asset-tracer/commit/4c429e921228259f47795f8ad913ad3eff8fac71)).
# Version 1.19 (2025-06-16)
- Add support for tracing dynamic paint caches ([#92889](https://projects.blender.org/blender/blender-asset-tracer/pulls/92889)).
- Add support for the large blendfile header blocks ([#92893](https://projects.blender.org/blender/blender-asset-tracer/pulls/92893)). This is necessary for compatibility with Blender 5.0.
- Drop support for Python 3.8.
# Version 1.18 (2024-01-11)
- When logging that there is no reader implemented for a certain data-block type, the filepath of the blend file that contains that data-block is now included in the message ([#92885](https://projects.blender.org/blender/blender-asset-tracer/pulls/92885)).
- Add support for tracing OpenVDB files ([#92884](https://projects.blender.org/blender/blender-asset-tracer/pulls/92884)).
- Add support for reading `uint8_t` (aka `uchar`, aka `unsigned char`).
- Fix issue packing lamps with IES files outside of the project folder ([#92886](https://projects.blender.org/blender/blender-asset-tracer/pulls/92886))
# Version 1.17 (2023-12-14)
- Fix issue packing lamps with non-IES 'storage' (File as Flamenco [issue #104269](https://projects.blender.org/studio/flamenco/issues/104269)).
# Version 1.16 (2023-11-02)
- Add `BlendFileBlock.raw_data()` and `.as_string()` functions. These functions interpret the data in a `BlendFileBlock` as either `bytes` or `string`. This can be used to obtain the contents of a `char*` (instead of the more common embedded `char[N]` array).
- Add support for IES lights ([#92883](https://projects.blender.org/blender/blender-asset-tracer/pulls/92883)).
- Fix issue packing 'directory' assets (like fluid simulation caches; [#104259](https://projects.blender.org/studio/flamenco/issues/104259)).
- Drop support for Python 3.7.
# Version 1.15 (2022-12-16)
- Add support for fluid simulation caches.
- Add support for Python 3.11
# Version 1.14 (2022-09-12)
- While tracing dependencies, call the progress callback function before opening a blend file, instead of before iterating over its contents. The opening (and loading of SDNA) takes a significant amount of time, so this will make any UI (like the Flamenco add-on) report the right filename when waiting for big files.
# Version 1.13 (2022-07-14) # Version 1.13 (2022-07-14)
- Improve an error message when packing fails. It now not only mentions that something went wrong, but also which file and which operation on that file (copy or move) was involved. - Improve an error message when packing fails. It now not only mentions that something went wrong, but also which file and which operation on that file (copy or move) was involved.

131
README.md
View File

@ -1,4 +1,4 @@
# Blender Asset Tracer BAT🦇 # Blender Asset Tracer BAT🦇 (ADV fork)
Script to manage assets with Blender. Script to manage assets with Blender.
@ -8,15 +8,23 @@ Blender Asset Tracer, a.k.a. BAT🦇, is the replacement of
Development is driven by choices explained in [T54125](https://developer.blender.org/T54125). Development is driven by choices explained in [T54125](https://developer.blender.org/T54125).
## Setting up development environment ## Basic access to command line operations
The `cli.py` wrapper at the root of the project can be used to directly access the command line
tools, without requiring any setup involving `venv` and so on:
``` ```
python3.9 -m venv .venv python3 path/to/repo/cli.py list path/to/blendfile.blend
. ./.venv/bin/activate ```
pip install -U pip
pip install poetry black ## Setting up development environment
poetry install
mypy --install-types First install [Poetry](https://python-poetry.org/). Because BAT has different
requirements than Poetry itself, it is recommended to install Poetry outside the
virtualenv you use for BAT. After that, run:
```
poetry install --all-extras --all-groups
``` ```
@ -67,49 +75,51 @@ Mypy likes to see the return type of `__init__` methods explicitly declared as `
BAT can be used as a Python library to inspect the contents of blend files, without having to BAT can be used as a Python library to inspect the contents of blend files, without having to
open Blender itself. Here is an example showing how to determine the render engine used: open Blender itself. Here is an example showing how to determine the render engine used:
#!/usr/bin/env python3.7 ```python
import json #!/usr/bin/env python3.7
import sys import json
from pathlib import Path import sys
from pathlib import Path
from blender_asset_tracer import blendfile from blender_asset_tracer import blendfile
from blender_asset_tracer.blendfile import iterators from blender_asset_tracer.blendfile import iterators
if len(sys.argv) != 2: if len(sys.argv) != 2:
print(f'Usage: {sys.argv[0]} somefile.blend', file=sys.stderr) print(f'Usage: {sys.argv[0]} somefile.blend', file=sys.stderr)
sys.exit(1) sys.exit(1)
bf_path = Path(sys.argv[1]) bf_path = Path(sys.argv[1])
bf = blendfile.open_cached(bf_path) bf = blendfile.open_cached(bf_path)
# Get the first window manager (there is probably exactly one). # Get the first window manager (there is probably exactly one).
window_managers = bf.find_blocks_from_code(b'WM') window_managers = bf.find_blocks_from_code(b'WM')
assert window_managers, 'The Blend file has no window manager' assert window_managers, 'The Blend file has no window manager'
window_manager = window_managers[0] window_manager = window_managers[0]
# Get the scene from the first window. # Get the scene from the first window.
windows = window_manager.get_pointer((b'windows', b'first')) windows = window_manager.get_pointer((b'windows', b'first'))
for window in iterators.listbase(windows): for window in iterators.listbase(windows):
scene = window.get_pointer(b'scene') scene = window.get_pointer(b'scene')
break break
# BAT can only return simple values, so it can't return the embedded # BAT can only return simple values, so it can't return the embedded
# struct 'r'. 'r.engine' is a simple string, though. # struct 'r'. 'r.engine' is a simple string, though.
engine = scene[b'r', b'engine'].decode('utf8') engine = scene[b'r', b'engine'].decode('utf8')
xsch = scene[b'r', b'xsch'] xsch = scene[b'r', b'xsch']
ysch = scene[b'r', b'ysch'] ysch = scene[b'r', b'ysch']
size = scene[b'r', b'size'] / 100.0 size = scene[b'r', b'size'] / 100.0
render_info = { render_info = {
'engine': engine, 'engine': engine,
'frame_pixels': { 'frame_pixels': {
'x': int(xsch * size), 'x': int(xsch * size),
'y': int(ysch * size), 'y': int(ysch * size),
}, },
} }
json.dump(render_info, sys.stdout, indent=4, sort_keys=True) json.dump(render_info, sys.stdout, indent=4, sort_keys=True)
print() print()
```
To understand the naming of the properties, look at Blender's `DNA_xxxx.h` files with struct To understand the naming of the properties, look at Blender's `DNA_xxxx.h` files with struct
definitions. It is those names that are accessed via `blender_asset_tracer.blendfile`. The definitions. It is those names that are accessed via `blender_asset_tracer.blendfile`. The
@ -131,3 +141,38 @@ This requirement helps to keep Blender add-ons separated, as an add-on can
import the modules of BAT it needs, then remove them from `sys.modules` and import the modules of BAT it needs, then remove them from `sys.modules` and
`sys.path` so that other add-ons don't see them. This should reduce problems `sys.path` so that other add-ons don't see them. This should reduce problems
with various add-ons shipping different versions of BAT. with various add-ons shipping different versions of BAT.
## Publishing a New Release
For uploading packages to PyPi, an API key is required; username+password will
not work.
First, generate an API token at https://pypi.org/manage/account/token/. Then,
use this token when publishing instead of your username and password.
As username, use `__token__`.
As password, use the token itself, including the `pypi-` prefix.
See https://pypi.org/help/#apitoken for help using API tokens to publish. This
is what I have in `~/.pypirc`:
```
[distutils]
index-servers =
bat
# Use `twine upload -r bat` to upload with this token.
[bat]
repository = https://upload.pypi.org/legacy/
username = __token__
password = pypi-abc-123-blablabla
```
```
. ./.venv/bin/activate
pip install twine
poetry build
poetry run twine check dist/blender_asset_tracer-1.21.tar.gz dist/blender_asset_tracer-1.21-*.whl
poetry run twine upload -r bat dist/blender_asset_tracer-1.21.tar.gz dist/blender_asset_tracer-1.21-*.whl
```

View File

@ -20,4 +20,57 @@
# <pep8 compliant> # <pep8 compliant>
__version__ = "1.13" __version__ = "1.21"
bl_info = {
"name": "Blender Asset Tracer",
"author": "Campbell Barton, Sybren A. St\u00fcvel, Lo\u00efc Charri\u00e8re, Cl\u00e9ment Ducarteron, Mario Hawat, Joseph Henry",
"version": (1, 21, 0),
"blender": (2, 80, 0),
"location": "File > External Data > BAT",
"description": "Utility for packing blend files",
"warning": "",
"wiki_url": "https://developer.blender.org/project/profile/79/",
"category": "Import-Export",
}
# Reset root module name if folder has an unexpected name
# (like "blender_asset_tracer-main" from zip-dl)
import sys
if __name__ != "blender_asset_tracer":
sys.modules["blender_asset_tracer"] = sys.modules[__name__]
try:
import bpy
_HAS_BPY = True
except ImportError:
_HAS_BPY = False
if _HAS_BPY:
from blender_asset_tracer import blendfile
from . import preferences, operators
# Match the CLI's default: skip dangling pointers gracefully instead of crashing.
# Production blend files often have references to missing linked libraries.
blendfile.set_strict_pointer_mode(False)
classes = (
preferences.BATPreferences,
operators.ExportBatPack,
operators.BAT_OT_export_zip,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.TOPBAR_MT_file_external_data.append(operators.menu_func)
def unregister():
bpy.types.TOPBAR_MT_file_external_data.remove(operators.menu_func)
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()

View File

@ -0,0 +1,3 @@
from blender_asset_tracer import cli
cli.cli_main()

View File

@ -122,6 +122,7 @@ class BlendFile:
self.filepath = path self.filepath = path
self.raw_filepath = path self.raw_filepath = path
self._is_modified = False self._is_modified = False
self.file_subversion = 0
self.fileobj = self._open_file(path, mode) self.fileobj = self._open_file(path, mode)
self.blocks = [] # type: BFBList self.blocks = [] # type: BFBList
@ -135,7 +136,7 @@ class BlendFile:
self.block_from_addr = {} # type: typing.Dict[int, BlendFileBlock] self.block_from_addr = {} # type: typing.Dict[int, BlendFileBlock]
self.header = header.BlendFileHeader(self.fileobj, self.raw_filepath) self.header = header.BlendFileHeader(self.fileobj, self.raw_filepath)
self.block_header_struct = self.header.create_block_header_struct() self.block_header_struct, self.block_header_fields = self.header.create_block_header_struct()
self._load_blocks() self._load_blocks()
def _open_file(self, path: pathlib.Path, mode: str) -> typing.IO[bytes]: def _open_file(self, path: pathlib.Path, mode: str) -> typing.IO[bytes]:
@ -169,6 +170,8 @@ class BlendFile:
if block.code == b"DNA1": if block.code == b"DNA1":
self.decode_structs(block) self.decode_structs(block)
elif block.code == b"GLOB":
self.decode_glob(block)
else: else:
self.fileobj.seek(block.size, os.SEEK_CUR) self.fileobj.seek(block.size, os.SEEK_CUR)
@ -356,6 +359,25 @@ class BlendFile:
dna_struct.append_field(field) dna_struct.append_field(field)
dna_offset += dna_size dna_offset += dna_size
def decode_glob(self, block: "BlendFileBlock") -> None:
"""Partially decode the GLOB block to get the file sub-version."""
# Before this, the subversion didn't exist in 'FileGlobal'.
if self.header.version <= 242:
self.file_subversion = 0
return
# GLOB can appear in the file before DNA1, and so we cannot use DNA to
# parse the fields.
# The subversion is always the `short` at offset 4.
# block_data = io.BytesIO(block.raw_data())
endian = self.header.endian
self.fileobj.seek(4, os.SEEK_CUR) # Skip the next 4 bytes.
self.file_subversion = endian.read_short(self.fileobj)
# Skip to the next block.
self.fileobj.seek(block.file_offset + block.size, os.SEEK_SET)
def abspath(self, relpath: bpathlib.BlendPath) -> bpathlib.BlendPath: def abspath(self, relpath: bpathlib.BlendPath) -> bpathlib.BlendPath:
"""Construct an absolute path from a blendfile-relative path.""" """Construct an absolute path from a blendfile-relative path."""
@ -425,6 +447,12 @@ class BlendFileBlock:
old_structure = struct.Struct(b"4sI") old_structure = struct.Struct(b"4sI")
"""old blend files ENDB block structure""" """old blend files ENDB block structure"""
# Explicitly annotate to avoid `Any` from `.unpack()`.
size: int
addr_old: int
sdna_index: int
count: int
def __init__(self, bfile: BlendFile) -> None: def __init__(self, bfile: BlendFile) -> None:
self.bfile = bfile self.bfile = bfile
@ -455,23 +483,13 @@ class BlendFileBlock:
self.code = b"ENDB" self.code = b"ENDB"
return return
# header size can be 8, 20, or 24 bytes long blockheader = bfile.block_header_fields(*header_struct.unpack(data))
# 8: old blend files ENDB block (exception) self.code = self.endian.read_data0(blockheader.code)
# 20: normal headers 32 bit platform
# 24: normal headers 64 bit platform
if len(data) <= 15:
self.log.debug("interpreting block as old-style ENB block")
blockheader = self.old_structure.unpack(data)
self.code = self.endian.read_data0(blockheader[0])
return
blockheader = header_struct.unpack(data)
self.code = self.endian.read_data0(blockheader[0])
if self.code != b"ENDB": if self.code != b"ENDB":
self.size = blockheader[1] self.size = blockheader.len
self.addr_old = blockheader[2] self.addr_old = blockheader.old
self.sdna_index = blockheader[3] self.sdna_index = blockheader.SDNAnr
self.count = blockheader[4] self.count = blockheader.nr
self.file_offset = bfile.fileobj.tell() self.file_offset = bfile.fileobj.tell()
def __repr__(self) -> str: def __repr__(self) -> str:
@ -578,6 +596,7 @@ class BlendFileBlock:
null_terminated=True, null_terminated=True,
as_str=False, as_str=False,
return_field=False, return_field=False,
array_index=0,
) -> typing.Any: ) -> typing.Any:
"""Read a property and return the value. """Read a property and return the value.
@ -594,8 +613,20 @@ class BlendFileBlock:
(assumes UTF-8 encoding). (assumes UTF-8 encoding).
:param return_field: When True, returns tuple (dna.Field, value). :param return_field: When True, returns tuple (dna.Field, value).
Otherwise just returns the value. Otherwise just returns the value.
:param array_index: If the property is an array, this determines the
index of the returned item from that array. Also see
`blendfile.iterators.dynamic_array()` for iterating such arrays.
""" """
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET) file_offset = self.file_offset
if array_index:
if not (0 <= array_index < self.count):
raise IndexError(
"Invalid 'array_index' for file-block. "
f"Expected int value in range 0-{self.count - 1}, got {array_index}."
)
file_offset += array_index * self.dna_type.size
self.bfile.fileobj.seek(file_offset, os.SEEK_SET)
dna_struct = self.bfile.structs[self.sdna_index] dna_struct = self.bfile.structs[self.sdna_index]
field, value = dna_struct.field_get( field, value = dna_struct.field_get(
@ -610,6 +641,27 @@ class BlendFileBlock:
return value, field return value, field
return value return value
def raw_data(self) -> bytes:
"""Read low-level raw data of this datablock."""
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
return self.bfile.fileobj.read(self.size)
def as_bytes_string(self) -> bytes:
"""Interpret the bytes of this datablock as null-terminated string of raw bytes."""
the_bytes = self.raw_data()
try:
first_null = the_bytes.index(0)
except ValueError:
pass
else:
the_bytes = the_bytes[:first_null]
return the_bytes
def as_string(self) -> str:
"""Interpret the bytes of this datablock as null-terminated utf8 string."""
the_bytes = self.as_bytes_string()
return the_bytes.decode()
def get_recursive_iter( def get_recursive_iter(
self, self,
path: dna.FieldPath, path: dna.FieldPath,
@ -674,7 +726,7 @@ class BlendFileBlock:
hsh = zlib.adler32(str(value).encode(), hsh) hsh = zlib.adler32(str(value).encode(), hsh)
return hsh return hsh
def set(self, path: bytes, value): def set(self, path: dna.FieldPath, value):
dna_struct = self.bfile.structs[self.sdna_index] dna_struct = self.bfile.structs[self.sdna_index]
self.bfile.mark_modified() self.bfile.mark_modified()
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET) self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
@ -783,9 +835,13 @@ class BlendFileBlock:
def __getitem__(self, path: dna.FieldPath): def __getitem__(self, path: dna.FieldPath):
return self.get(path) return self.get(path)
def __setitem__(self, item: bytes, value) -> None: def __setitem__(self, item: dna.FieldPath, value) -> None:
self.set(item, value) self.set(item, value)
def has_field(self, name: bytes) -> bool:
dna_struct = self.bfile.structs[self.sdna_index]
return dna_struct.has_field(name)
def keys(self) -> typing.Iterator[bytes]: def keys(self) -> typing.Iterator[bytes]:
"""Generator, yields all field names of this block.""" """Generator, yields all field names of this block."""
return (f.name.name_only for f in self.dna_type.fields) return (f.name.name_only for f in self.dna_type.fields)

View File

@ -22,6 +22,7 @@
import logging import logging
import os import os
import typing import typing
from typing import Optional
from . import header, exceptions from . import header, exceptions
@ -104,7 +105,7 @@ class Struct:
log = log.getChild("Struct") log = log.getChild("Struct")
def __init__(self, dna_type_id: bytes, size: int = None) -> None: def __init__(self, dna_type_id: bytes, size: Optional[int] = None) -> None:
""" """
:param dna_type_id: name of the struct in C, like b'AlembicObjectPath'. :param dna_type_id: name of the struct in C, like b'AlembicObjectPath'.
:param size: only for unit tests; typically set after construction by :param size: only for unit tests; typically set after construction by
@ -254,10 +255,12 @@ class Struct:
) )
simple_readers = { simple_readers = {
b"uchar": endian.read_char,
b"int": endian.read_int, b"int": endian.read_int,
b"short": endian.read_short, b"short": endian.read_short,
b"uint64_t": endian.read_ulong, b"uint64_t": endian.read_ulong,
b"float": endian.read_float, b"float": endian.read_float,
b"int8_t": endian.read_int8,
} }
try: try:
simple_reader = simple_readers[dna_type.dna_type_id] simple_reader = simple_readers[dna_type.dna_type_id]
@ -307,7 +310,7 @@ class Struct:
self, self,
file_header: header.BlendFileHeader, file_header: header.BlendFileHeader,
fileobj: typing.IO[bytes], fileobj: typing.IO[bytes],
path: bytes, path: FieldPath,
value: typing.Any, value: typing.Any,
): ):
"""Write a value to the blend file. """Write a value to the blend file.
@ -316,7 +319,6 @@ class Struct:
struct on disk (e.g. the start of the BlendFileBlock containing the struct on disk (e.g. the start of the BlendFileBlock containing the
data). data).
""" """
assert isinstance(path, bytes), "path should be bytes, but is %s" % type(path)
field, offset = self.field_from_path(file_header.pointer_size, path) field, offset = self.field_from_path(file_header.pointer_size, path)

View File

@ -28,6 +28,7 @@ import typing
class EndianIO: class EndianIO:
# TODO(Sybren): note as UCHAR: struct.Struct = None and move actual structs to LittleEndianTypes # TODO(Sybren): note as UCHAR: struct.Struct = None and move actual structs to LittleEndianTypes
UCHAR = struct.Struct(b"<B") UCHAR = struct.Struct(b"<B")
SINT8 = struct.Struct(b"<b")
USHORT = struct.Struct(b"<H") USHORT = struct.Struct(b"<H")
USHORT2 = struct.Struct(b"<HH") # two shorts in a row USHORT2 = struct.Struct(b"<HH") # two shorts in a row
SSHORT = struct.Struct(b"<h") SSHORT = struct.Struct(b"<h")
@ -62,6 +63,14 @@ class EndianIO:
def write_char(cls, fileobj: typing.IO[bytes], value: int): def write_char(cls, fileobj: typing.IO[bytes], value: int):
return cls._write(fileobj, cls.UCHAR, value) return cls._write(fileobj, cls.UCHAR, value)
@classmethod
def read_int8(cls, fileobj: typing.IO[bytes]):
return cls._read(fileobj, cls.SINT8)
@classmethod
def write_int8(cls, fileobj: typing.IO[bytes], value: int):
return cls._write(fileobj, cls.SINT8, value)
@classmethod @classmethod
def read_ushort(cls, fileobj: typing.IO[bytes]): def read_ushort(cls, fileobj: typing.IO[bytes]):
return cls._read(fileobj, cls.USHORT) return cls._read(fileobj, cls.USHORT)
@ -120,6 +129,20 @@ class EndianIO:
return cls.read_ulong(fileobj) return cls.read_ulong(fileobj)
raise ValueError("unsupported pointer size %d" % pointer_size) raise ValueError("unsupported pointer size %d" % pointer_size)
@classmethod
def parse_pointer(cls, pointer_data: bytes):
"""Parse bytes as a pointer value."""
pointer_size = len(pointer_data)
try:
typestruct = {
4: cls.UINT,
8: cls.ULONG,
}[pointer_size]
except KeyError:
raise ValueError("unsupported pointer size %d" % pointer_size)
return typestruct.unpack(pointer_data)[0]
@classmethod @classmethod
def write_pointer(cls, fileobj: typing.IO[bytes], pointer_size: int, value: int): def write_pointer(cls, fileobj: typing.IO[bytes], pointer_size: int, value: int):
"""Write a pointer to a file.""" """Write a pointer to a file."""
@ -181,17 +204,17 @@ class EndianIO:
return fileobj.write(to_write) return fileobj.write(to_write)
@classmethod @classmethod
def read_bytes0(cls, fileobj, length): def read_bytes0(cls, fileobj: typing.IO[bytes], length: int) -> bytes:
data = fileobj.read(length) data = fileobj.read(length)
return cls.read_data0(data) return cls.read_data0(data)
@classmethod @classmethod
def read_data0_offset(cls, data, offset): def read_data0_offset(cls, data: bytes, offset: int) -> bytes:
add = data.find(b"\0", offset) - offset add = data.find(b"\0", offset) - offset
return data[offset : offset + add] return data[offset : offset + add]
@classmethod @classmethod
def read_data0(cls, data): def read_data0(cls, data: bytes) -> bytes:
add = data.find(b"\0") add = data.find(b"\0")
if add < 0: if add < 0:
return data return data
@ -207,6 +230,7 @@ class EndianIO:
""" """
return { return {
b"char": cls.write_char, b"char": cls.write_char,
b"int8": cls.write_int8,
b"ushort": cls.write_ushort, b"ushort": cls.write_ushort,
b"short": cls.write_short, b"short": cls.write_short,
b"uint": cls.write_uint, b"uint": cls.write_uint,
@ -222,6 +246,7 @@ class LittleEndianTypes(EndianIO):
class BigEndianTypes(LittleEndianTypes): class BigEndianTypes(LittleEndianTypes):
UCHAR = struct.Struct(b">B") UCHAR = struct.Struct(b">B")
SINT8 = struct.Struct(b">b")
USHORT = struct.Struct(b">H") USHORT = struct.Struct(b">H")
USHORT2 = struct.Struct(b">HH") # two shorts in a row USHORT2 = struct.Struct(b">HH") # two shorts in a row
SSHORT = struct.Struct(b">h") SSHORT = struct.Struct(b">h")

View File

@ -19,6 +19,8 @@
# (c) 2009, At Mind B.V. - Jeroen Bakker # (c) 2009, At Mind B.V. - Jeroen Bakker
# (c) 2014, Blender Foundation - Campbell Barton # (c) 2014, Blender Foundation - Campbell Barton
# (c) 2018, Blender Foundation - Sybren A. Stüvel # (c) 2018, Blender Foundation - Sybren A. Stüvel
from dataclasses import dataclass
import logging import logging
import os import os
import pathlib import pathlib
@ -30,58 +32,151 @@ from . import dna_io, exceptions
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@dataclass
class BHead4:
code: bytes
len: int
old: int
SDNAnr: int
nr: int
@dataclass
class SmallBHead8:
code: bytes
len: int
old: int
SDNAnr: int
nr: int
@dataclass
class LargeBHead8:
code: bytes
SDNAnr: int
old: int
len: int
nr: int
class BlendFileHeader: class BlendFileHeader:
""" """
BlendFileHeader represents the first 12 bytes of a blend file. BlendFileHeader represents the first 12-17 bytes of a blend file.
It contains information about the hardware architecture, which is relevant It contains information about the hardware architecture, which is relevant
to the structure of the rest of the file. to the structure of the rest of the file.
""" """
structure = struct.Struct(b"7s1s1s3s") magic: bytes
file_format_version: int
pointer_size: int
is_little_endian: bool
endian: typing.Type[dna_io.EndianIO]
endian_str: bytes
def __init__(self, fileobj: typing.IO[bytes], path: pathlib.Path) -> None: def __init__(self, fileobj: typing.IO[bytes], path: pathlib.Path) -> None:
log.debug("reading blend-file-header %s", path) log.debug("reading blend-file-header %s", path)
fileobj.seek(0, os.SEEK_SET) fileobj.seek(0, os.SEEK_SET)
header = fileobj.read(self.structure.size)
values = self.structure.unpack(header)
self.magic = values[0] bytes_0_6 = fileobj.read(7)
if bytes_0_6 != b'BLENDER':
raise exceptions.BlendFileError("invalid first bytes %r" % bytes_0_6, path)
self.magic = bytes_0_6
pointer_size_id = values[1] byte_7 = fileobj.read(1)
if pointer_size_id == b"-": is_legacy_header = byte_7 in (b'_', b'-')
if is_legacy_header:
self.file_format_version = 0
if byte_7 == b'_':
self.pointer_size = 4
elif byte_7 == b'-':
self.pointer_size = 8
else:
raise exceptions.BlendFileError("invalid pointer size %r" % byte_7, path)
byte_8 = fileobj.read(1)
if byte_8 == b'v':
self.is_little_endian = True
elif byte_8 == b'V':
self.is_little_endian = False
else:
raise exceptions.BlendFileError("invalid endian indicator %r" % byte_8, path)
bytes_9_11 = fileobj.read(3)
self.version = int(bytes_9_11)
else:
byte_8 = fileobj.read(1)
header_size = int(byte_7 + byte_8)
if header_size != 17:
raise exceptions.BlendFileError("unknown file header size %d" % header_size, path)
byte_9 = fileobj.read(1)
if byte_9 != b'-':
raise exceptions.BlendFileError("invalid file header", path)
self.pointer_size = 8 self.pointer_size = 8
elif pointer_size_id == b"_": byte_10_11 = fileobj.read(2)
self.pointer_size = 4 self.file_format_version = int(byte_10_11)
else: if self.file_format_version != 1:
raise exceptions.BlendFileError( raise exceptions.BlendFileError("unsupported file format version %r" % self.file_format_version, path)
"invalid pointer size %r" % pointer_size_id, path byte_12 = fileobj.read(1)
) if byte_12 != b'v':
raise exceptions.BlendFileError("invalid file header", path)
self.is_little_endian = True
byte_13_16 = fileobj.read(4)
self.version = int(byte_13_16)
endian_id = values[2] if self.is_little_endian:
if endian_id == b"v": self.endian_str = b'<'
self.endian = dna_io.LittleEndianTypes self.endian = dna_io.LittleEndianTypes
self.endian_str = b"<" # indication for struct.Struct()
elif endian_id == b"V":
self.endian = dna_io.BigEndianTypes
self.endian_str = b">" # indication for struct.Struct()
else: else:
raise exceptions.BlendFileError( self.endian_str = b'>'
"invalid endian indicator %r" % endian_id, path self.endian = dna_io.BigEndianTypes
)
version_id = values[3] def create_block_header_struct(self) -> typing.Tuple[struct.Struct, typing.Type[typing.Union[BHead4, SmallBHead8, LargeBHead8]]]:
self.version = int(version_id) """
Returns a Struct instance for parsing data block headers and a corresponding
Python class for accessing the right members. Ddepending on the .blend file,
the order of the data members in the block header may be different.
"""
assert self.file_format_version in (0, 1)
if self.file_format_version == 1:
header_struct = struct.Struct(b''.join((
self.endian_str,
# LargeBHead8.code
b'4s',
# LargeBHead8.SDNAnr
b'i',
# LargeBHead8.old
b'Q',
# LargeBHead8.len
b'q',
# LargeBHead8.nr
b'q',
)))
return header_struct, LargeBHead8
def create_block_header_struct(self) -> struct.Struct: if self.pointer_size == 4:
"""Create a Struct instance for parsing data block headers.""" header_struct = struct.Struct(b''.join((
return struct.Struct( self.endian_str,
b"".join( # BHead4.code
( b'4s',
self.endian_str, # BHead4.len
b"4sI", b'i',
b"I" if self.pointer_size == 4 else b"Q", # BHead4.old
b"II", b'I',
) # BHead4.SDNAnr
) b'i',
) # BHead4.nr
b'i',
)))
return header_struct, BHead4
assert self.pointer_size == 8
header_struct = struct.Struct(b''.join((
self.endian_str,
# SmallBHead8.code
b'4s',
# SmallBHead8.len
b'i',
# SmallBHead8.old
b'Q',
# SmallBHead8.SDNAnr
b'i',
# SmallBHead8.nr
b'i',
)))
return header_struct, SmallBHead8

View File

@ -20,6 +20,7 @@
# (c) 2014, Blender Foundation - Campbell Barton # (c) 2014, Blender Foundation - Campbell Barton
# (c) 2018, Blender Foundation - Sybren A. Stüvel # (c) 2018, Blender Foundation - Sybren A. Stüvel
import typing import typing
import copy
from blender_asset_tracer import cdefs from blender_asset_tracer import cdefs
from . import BlendFileBlock from . import BlendFileBlock
@ -70,3 +71,27 @@ def modifiers(object_block: BlendFileBlock) -> typing.Iterator[BlendFileBlock]:
# 'ob->modifiers[...]' # 'ob->modifiers[...]'
mods = object_block.get_pointer((b"modifiers", b"first")) mods = object_block.get_pointer((b"modifiers", b"first"))
yield from listbase(mods, next_path=(b"modifier", b"next")) yield from listbase(mods, next_path=(b"modifier", b"next"))
def dynamic_array(block: BlendFileBlock) -> typing.Iterator[BlendFileBlock]:
"""
Generator that yields each element of a dynamic array as a separate block.
Dynamic arrays are multiple contiguous elements accessed via a single
pointer. BAT interprets these as a single data block, making it hard to
access individual elements. This function divides the array into individual
blocks by creating modified copies of the original block.
See `some_block.get(b'name', array_index)` if you want to access elements by
index (instead of iterating).
"""
element_size = block.dna_type.size
sub_block = copy.copy(block)
sub_block.size = element_size
for i in range(block.count):
# When sub_block's data is read, it'll be read from this offset in the blend file.
sub_block.file_offset = block.file_offset + i * element_size
yield sub_block

View File

@ -46,10 +46,20 @@ eModifierType_WeightVGEdit = 36
eModifierType_WeightVGMix = 37 eModifierType_WeightVGMix = 37
eModifierType_WeightVGProximity = 38 eModifierType_WeightVGProximity = 38
eModifierType_Ocean = 39 eModifierType_Ocean = 39
eModifierType_DynamicPaint = 40
eModifierType_MeshCache = 46 eModifierType_MeshCache = 46
eModifierType_MeshSequenceCache = 52 eModifierType_MeshSequenceCache = 52
eModifierType_Fluid = 56
eModifierType_Nodes = 57 eModifierType_Nodes = 57
# NodesModifierBakeFlag
NODES_MODIFIER_BAKE_CUSTOM_PATH = 1 << 1
# NodesModifierBakeTarget
NODES_MODIFIER_BAKE_TARGET_INHERIT = 0
NODES_MODIFIER_BAKE_TARGET_PACKED = 1
NODES_MODIFIER_BAKE_TARGET_DISK = 2
# DNA_particle_types.h # DNA_particle_types.h
PART_DRAW_OB = 7 PART_DRAW_OB = 7
PART_DRAW_GR = 8 PART_DRAW_GR = 8

View File

@ -112,7 +112,7 @@ def cli_blocks(args):
# From the blocks of the most space-using category, the biggest block. # From the blocks of the most space-using category, the biggest block.
biggest_block = sorted(infos[0].blocks, key=lambda blck: blck.size, reverse=True)[0] biggest_block = sorted(infos[0].blocks, key=lambda blck: blck.size, reverse=True)[0]
print( print(
"Biggest %s block is %s at address %s" "Biggest %s block is %s at address 0x%x"
% ( % (
block_key(biggest_block), block_key(biggest_block),
common.humanize_bytes(biggest_block.size), common.humanize_bytes(biggest_block.size),

21
blender_asset_tracer/cli/pack.py Normal file → Executable file
View File

@ -88,6 +88,15 @@ def add_parser(subparsers):
help="Only pack assets that are referred to with a relative path (e.g. " help="Only pack assets that are referred to with a relative path (e.g. "
"starting with `//`.", "starting with `//`.",
) )
parser.add_argument(
"--keep-hierarchy",
default=False,
action="store_true",
help="Preserve the full filesystem directory hierarchy in the pack. "
"All files (including the blend file) are placed at their absolute "
"path structure under the target directory. Paths in blend files are "
"rewritten to relative paths within this structure.",
)
def cli_pack(args): def cli_pack(args):
@ -119,6 +128,9 @@ def create_packer(
if args.relative_only: if args.relative_only:
raise ValueError("S3 uploader does not support the --relative-only option") raise ValueError("S3 uploader does not support the --relative-only option")
if args.keep_hierarchy:
raise ValueError("S3 uploader does not support the --keep-hierarchy option")
packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target)) packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target))
elif ( elif (
@ -137,6 +149,11 @@ def create_packer(
"Shaman uploader does not support the --relative-only option" "Shaman uploader does not support the --relative-only option"
) )
if args.keep_hierarchy:
raise ValueError(
"Shaman uploader does not support the --keep-hierarchy option"
)
packer = create_shamanpacker(bpath, ppath, target) packer = create_shamanpacker(bpath, ppath, target)
elif target.lower().endswith(".zip"): elif target.lower().endswith(".zip"):
@ -146,7 +163,8 @@ def create_packer(
raise ValueError("ZIP packer does not support on-the-fly compression") raise ValueError("ZIP packer does not support on-the-fly compression")
packer = zipped.ZipPacker( packer = zipped.ZipPacker(
bpath, ppath, target, noop=args.noop, relative_only=args.relative_only bpath, ppath, target, noop=args.noop, relative_only=args.relative_only,
keep_hierarchy=args.keep_hierarchy,
) )
else: else:
packer = pack.Packer( packer = pack.Packer(
@ -156,6 +174,7 @@ def create_packer(
noop=args.noop, noop=args.noop,
compress=args.compress, compress=args.compress,
relative_only=args.relative_only, relative_only=args.relative_only,
keep_hierarchy=args.keep_hierarchy,
) )
if args.exclude: if args.exclude:

View File

@ -0,0 +1,163 @@
import os
import sys
import subprocess
import tempfile
import zipfile
from pathlib import Path
import bpy
from bpy.types import Operator
from bpy_extras.io_utils import ExportHelper
from blender_asset_tracer.pack import zipped
class ExportBatPack(Operator, ExportHelper):
bl_idname = "export_bat.pack"
bl_label = "BAT - Zip pack (flat)"
filename_ext = ".zip"
@classmethod
def poll(cls, context):
return bpy.data.is_saved
def execute(self, context):
outfname = bpy.path.ensure_ext(self.filepath, ".zip")
self.report({"INFO"}, "Executing ZipPacker ...")
with zipped.ZipPacker(
Path(bpy.data.filepath),
Path(bpy.data.filepath).parent,
str(self.filepath),
) as packer:
packer.strategise()
packer.execute()
self.report({"INFO"}, "Packing successful!")
with zipfile.ZipFile(str(self.filepath)) as inzip:
inzip.testzip()
self.report({"INFO"}, "Written to %s" % outfname)
return {"FINISHED"}
def open_folder(folderpath):
"""Open the folder at the path given with cmd relative to user's OS."""
from shutil import which
my_os = sys.platform
if my_os.startswith(("linux", "freebsd")):
cmd = "xdg-open"
elif my_os.startswith("win"):
cmd = "explorer"
if not folderpath:
return
else:
cmd = "open"
if not folderpath:
return
folderpath = str(folderpath)
if os.path.isfile(folderpath):
select = False
if my_os.startswith("win"):
cmd = "explorer /select,"
select = True
elif my_os.startswith(("linux", "freebsd")):
if which("nemo"):
cmd = "nemo --no-desktop"
select = True
elif which("nautilus"):
cmd = "nautilus --no-desktop"
select = True
if not select:
folderpath = os.path.dirname(folderpath)
folderpath = os.path.normpath(folderpath)
fullcmd = cmd.split() + [folderpath]
subprocess.Popen(fullcmd)
class BAT_OT_export_zip(Operator, ExportHelper):
"""Export current blendfile with hierarchy preservation"""
bl_label = "BAT - Zip pack (keep hierarchy)"
bl_idname = "bat.export_zip"
filename_ext = ".zip"
root_dir: bpy.props.StringProperty(
name="Root",
description="Top Level Folder of your project."
"\nFor now Copy/Paste correct folder by hand if default is incorrect."
"\n!!! Everything outside won't be zipped !!!",
)
use_zip: bpy.props.BoolProperty(
name="Output as ZIP",
description="If enabled, pack into a ZIP archive. If disabled, copy to a directory.",
default=True,
)
@classmethod
def poll(cls, context):
return bpy.data.is_saved
def execute(self, context):
from blender_asset_tracer.pack import Packer
from blender_asset_tracer.pack.zipped import ZipPacker
bfile = Path(bpy.data.filepath)
project = Path(self.root_dir) if self.root_dir else bfile.parent
target = str(self.filepath)
if self.use_zip:
target = bpy.path.ensure_ext(target, ".zip")
packer_cls = ZipPacker
else:
if target.lower().endswith(".zip"):
target = target[:-4]
packer_cls = Packer
self.report({"INFO"}, "Packing with hierarchy...")
with packer_cls(bfile, project, target, keep_hierarchy=True) as packer:
packer.strategise()
packer.execute()
if self.use_zip:
with zipfile.ZipFile(target) as inzip:
inzip.testzip()
log_output = Path(tempfile.gettempdir(), "README.txt")
with open(log_output, "w") as log:
log.write("Packed with BAT (keep-hierarchy mode)")
log.write(f"\nBlend file: {bpy.data.filepath}")
with zipfile.ZipFile(target, "a") as zipObj:
zipObj.write(log_output, log_output.name)
self.report({"INFO"}, "Written to %s" % target)
open_folder(Path(target).parent)
return {"FINISHED"}
def menu_func(self, context):
layout = self.layout
layout.separator()
layout.operator(ExportBatPack.bl_idname)
filepath = layout.operator(BAT_OT_export_zip.bl_idname)
try:
prefs = bpy.context.preferences.addons["blender_asset_tracer"].preferences
root_dir_env = None
if prefs.use_env_root:
root_dir_env = os.getenv("ZIP_ROOT")
if not root_dir_env:
root_dir_env = os.getenv("PROJECT_ROOT")
if not root_dir_env:
root_dir_env = prefs.root_default
filepath.root_dir = "" if root_dir_env is None else root_dir_env
except Exception:
filepath.root_dir = os.getenv("ZIP_ROOT") or os.getenv("PROJECT_ROOT") or ""

55
blender_asset_tracer/pack/__init__.py Normal file → Executable file
View File

@ -102,7 +102,8 @@ class Packer:
*, *,
noop=False, noop=False,
compress=False, compress=False,
relative_only=False relative_only=False,
keep_hierarchy=False,
) -> None: ) -> None:
self.blendfile = bfile self.blendfile = bfile
self.project = project self.project = project
@ -111,6 +112,7 @@ class Packer:
self.noop = noop self.noop = noop
self.compress = compress self.compress = compress
self.relative_only = relative_only self.relative_only = relative_only
self.keep_hierarchy = keep_hierarchy
self._aborted = threading.Event() self._aborted = threading.Event()
self._abort_lock = threading.RLock() self._abort_lock = threading.RLock()
self._abort_reason = "" self._abort_reason = ""
@ -241,9 +243,12 @@ class Packer:
# network shares mapped to Windows drive letters back to their UNC # network shares mapped to Windows drive letters back to their UNC
# notation. Only resolving one but not the other (which can happen # notation. Only resolving one but not the other (which can happen
# with the abosolute() call above) can cause errors. # with the abosolute() call above) can cause errors.
bfile_pp = self._target_path / bfile_path.relative_to( if self.keep_hierarchy:
bpathlib.make_absolute(self.project) bfile_pp = self._target_path / bpathlib.strip_root(bfile_path)
) else:
bfile_pp = self._target_path / bfile_path.relative_to(
bpathlib.make_absolute(self.project)
)
self._output_path = bfile_pp self._output_path = bfile_pp
self._progress_cb.pack_start() self._progress_cb.pack_start()
@ -335,7 +340,10 @@ class Packer:
self._new_location_paths.add(asset_path) self._new_location_paths.add(asset_path)
else: else:
log.debug("%s can keep using %s", bfile_path, usage.asset_path) log.debug("%s can keep using %s", bfile_path, usage.asset_path)
asset_pp = self._target_path / asset_path.relative_to(self.project) if self.keep_hierarchy:
asset_pp = self._target_path / bpathlib.strip_root(asset_path)
else:
asset_pp = self._target_path / asset_path.relative_to(self.project)
act.new_path = asset_pp act.new_path = asset_pp
def _find_new_paths(self): def _find_new_paths(self):
@ -346,7 +354,10 @@ class Packer:
assert isinstance(act, AssetAction) assert isinstance(act, AssetAction)
relpath = bpathlib.strip_root(path) relpath = bpathlib.strip_root(path)
act.new_path = pathlib.Path(self._target_path, "_outside_project", relpath) if self.keep_hierarchy:
act.new_path = pathlib.Path(self._target_path, relpath)
else:
act.new_path = pathlib.Path(self._target_path, "_outside_project", relpath)
def _group_rewrites(self) -> None: def _group_rewrites(self) -> None:
"""For each blend file, collect which fields need rewriting. """For each blend file, collect which fields need rewriting.
@ -475,8 +486,9 @@ class Packer:
# It is *not* used for any disk I/O, since the file may not even # It is *not* used for any disk I/O, since the file may not even
# exist on the local filesystem. # exist on the local filesystem.
bfile_pp = action.new_path bfile_pp = action.new_path
assert bfile_pp is not None, \ assert (
f"Action {action.path_action.name} on {bfile_path} has no final path set, unable to process" bfile_pp is not None
), f"Action {action.path_action.name} on {bfile_path} has no final path set, unable to process"
# Use tempfile to create a unique name in our temporary directoy. # Use tempfile to create a unique name in our temporary directoy.
# The file should be deleted when self.close() is called, and not # The file should be deleted when self.close() is called, and not
@ -553,9 +565,15 @@ class Packer:
bfile.close() bfile.close()
def _copy_asset_and_deps(self, asset_path: pathlib.Path, action: AssetAction): def _copy_asset_and_deps(self, asset_path: pathlib.Path, action: AssetAction):
asset_path_is_dir = asset_path.is_dir()
# Copy the asset itself, but only if it's not a sequence (sequences are # Copy the asset itself, but only if it's not a sequence (sequences are
# handled below in the for-loop). # handled below in the for-loop).
if "*" not in str(asset_path) and "<UDIM>" not in asset_path.name: if (
"*" not in str(asset_path)
and "<UDIM>" not in asset_path.name
and not asset_path_is_dir
):
packed_path = action.new_path packed_path = action.new_path
assert packed_path is not None assert packed_path is not None
read_path = action.read_from or asset_path read_path = action.read_from or asset_path
@ -563,6 +581,11 @@ class Packer:
read_path, packed_path, may_move=action.read_from is not None read_path, packed_path, may_move=action.read_from is not None
) )
if asset_path_is_dir: # like 'some/directory':
asset_base_path = asset_path
else: # like 'some/directory/prefix_*.bphys':
asset_base_path = asset_path.parent
# Copy its sequence dependencies. # Copy its sequence dependencies.
for usage in action.usages: for usage in action.usages:
if not usage.is_sequence: if not usage.is_sequence:
@ -570,14 +593,24 @@ class Packer:
first_pp = self._actions[usage.abspath].new_path first_pp = self._actions[usage.abspath].new_path
assert first_pp is not None assert first_pp is not None
log.info(f"first_pp = {first_pp}")
# In case of globbing, we only support globbing by filename, # In case of globbing, we only support globbing by filename,
# and not by directory. # and not by directory.
assert "*" not in str(first_pp) or "*" in first_pp.name assert "*" not in str(first_pp) or "*" in first_pp.name
packed_base_dir = first_pp.parent if asset_path_is_dir:
packed_base_dir = first_pp
else:
packed_base_dir = first_pp.parent
for file_path in usage.files(): for file_path in usage.files():
packed_path = packed_base_dir / file_path.name # Compute the relative path, to support cases where asset_path
# is `some/directory` and the to-be-copied file is in
# `some/directory/subdir/filename.txt`.
relpath = file_path.relative_to(asset_base_path)
packed_path = packed_base_dir / relpath
# Assumption: assets in a sequence are never blend files. # Assumption: assets in a sequence are never blend files.
self._send_to_target(file_path, packed_path) self._send_to_target(file_path, packed_path)

View File

@ -25,6 +25,7 @@ import logging
import pathlib import pathlib
import queue import queue
import typing import typing
from typing import Optional
import blender_asset_tracer.trace.progress import blender_asset_tracer.trace.progress
@ -134,7 +135,7 @@ class ThreadSafeCallback(Callback):
def missing_file(self, filename: pathlib.Path) -> None: def missing_file(self, filename: pathlib.Path) -> None:
self._queue(self.wrapped.missing_file, filename) self._queue(self.wrapped.missing_file, filename)
def flush(self, timeout: float = None) -> None: def flush(self, timeout: Optional[float] = None) -> None:
"""Call the queued calls, call this in the main thread.""" """Call the queued calls, call this in the main thread."""
while True: while True:

View File

@ -25,6 +25,7 @@ import queue
import threading import threading
import time import time
import typing import typing
from typing import Optional
from . import progress from . import progress
@ -89,6 +90,8 @@ class FileTransferer(threading.Thread, metaclass=abc.ABCMeta):
def queue_copy(self, src: pathlib.Path, dst: pathlib.PurePath): def queue_copy(self, src: pathlib.Path, dst: pathlib.PurePath):
"""Queue a copy action from 'src' to 'dst'.""" """Queue a copy action from 'src' to 'dst'."""
if src.is_dir():
raise TypeError(f"only files can be copied, not directories: {src}")
assert ( assert (
not self.done.is_set() not self.done.is_set()
), "Queueing not allowed after done_and_join() was called" ), "Queueing not allowed after done_and_join() was called"
@ -102,6 +105,8 @@ class FileTransferer(threading.Thread, metaclass=abc.ABCMeta):
def queue_move(self, src: pathlib.Path, dst: pathlib.PurePath): def queue_move(self, src: pathlib.Path, dst: pathlib.PurePath):
"""Queue a move action from 'src' to 'dst'.""" """Queue a move action from 'src' to 'dst'."""
if src.is_dir():
raise TypeError(f"only files can be moved, not directories: {src}")
assert ( assert (
not self.done.is_set() not self.done.is_set()
), "Queueing not allowed after done_and_join() was called" ), "Queueing not allowed after done_and_join() was called"
@ -186,7 +191,7 @@ class FileTransferer(threading.Thread, metaclass=abc.ABCMeta):
if self.done.is_set(): if self.done.is_set():
return return
def join(self, timeout: float = None) -> None: def join(self, timeout: Optional[float] = None) -> None:
"""Wait for the transfer to finish/stop.""" """Wait for the transfer to finish/stop."""
if timeout: if timeout:

View File

@ -0,0 +1,27 @@
import bpy
from bpy.types import AddonPreferences
from bpy.props import BoolProperty, StringProperty
class BATPreferences(AddonPreferences):
bl_idname = "blender_asset_tracer"
use_env_root: BoolProperty(
name="Use Environment Variable for Root",
description="Read the project root from ZIP_ROOT or PROJECT_ROOT environment variables",
default=False,
)
root_default: StringProperty(
name="Default Root",
description="Fallback project root when the environment variable is not set",
default="",
subtype="DIR_PATH",
)
def draw(self, context):
layout = self.layout
layout.prop(self, "use_env_root")
layout.prop(self, "root_default")

View File

View File

@ -49,12 +49,10 @@ def deps(
:param progress_cb: Progress callback object. :param progress_cb: Progress callback object.
""" """
log.info("opening: %s", bfilepath)
bfile = blendfile.open_cached(bfilepath)
bi = file2blocks.BlockIterator() bi = file2blocks.BlockIterator()
if progress_cb: if progress_cb:
bi.progress_cb = progress_cb bi.progress_cb = progress_cb
bfile = bi.open_blendfile(bfilepath)
# Remember which block usages we've reported already, without keeping the # Remember which block usages we've reported already, without keeping the
# blocks themselves in memory. # blocks themselves in memory.

View File

@ -44,9 +44,20 @@ def iter_assets(block: blendfile.BlendFileBlock) -> typing.Iterator[result.Block
try: try:
block_reader = _funcs_for_code[block.code] block_reader = _funcs_for_code[block.code]
except KeyError: except KeyError:
if block.code not in _warned_about_types: if block.code in _warned_about_types:
log.debug("No reader implemented for block type %r", block.code.decode()) return
_warned_about_types.add(block.code)
blocktype = block.code.decode()
filepath = block.get(b"filepath", "", as_str=True)
if filepath:
log.debug(
"No reader implemented for block type %r (filepath=%r)",
blocktype,
filepath,
)
else:
log.debug("No reader implemented for block type %r", blocktype)
_warned_about_types.add(block.code)
return return
log.debug("Tracing block %r", block) log.debug("Tracing block %r", block)
@ -108,6 +119,7 @@ def image(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]
@dna_code("LI") @dna_code("LI")
@skip_packed
def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
"""Library data blocks.""" """Library data blocks."""
path, field = block.get(b"name", return_field=True) path, field = block.get(b"name", return_field=True)
@ -213,3 +225,34 @@ def vector_font(block: blendfile.BlendFileBlock) -> typing.Iterator[result.Block
if path == b"<builtin>": # builtin font if path == b"<builtin>": # builtin font
return return
yield result.BlockUsage(block, path, path_full_field=field) yield result.BlockUsage(block, path, path_full_field=field)
@dna_code("LA")
@skip_packed
def lamp(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
"""Lamp data blocks."""
block_ntree = block.get_pointer(b"nodetree", None)
if block_ntree is None:
return
for node in iterators.listbase(block_ntree.get_pointer((b"nodes", b"first"))):
storage = node.get_pointer(b"storage")
if not storage:
continue
# A storage block of `NodeShaderTexIES` type has a `filepath`, but not
# all types that can be pointed to by the `storage` pointer do.
path, field = storage.get(b"filepath", return_field=True, default=None)
if path is None:
continue
yield result.BlockUsage(storage, path, path_full_field=field)
@dna_code("VO")
@skip_packed
def open_vdb(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
"""OpenVDB data blocks."""
path, field = block.get(b"filepath", return_field=True)
is_sequence = bool(block.get(b"is_sequence"))
yield result.BlockUsage(block, path, path_full_field=field, is_sequence=is_sequence)

View File

@ -90,7 +90,14 @@ def _expand_generic_mtex(block: blendfile.BlendFileBlock):
def _expand_generic_nodetree(block: blendfile.BlendFileBlock): def _expand_generic_nodetree(block: blendfile.BlendFileBlock):
assert block.dna_type.dna_type_id == b"bNodeTree" if block.dna_type.dna_type_id == b"ID":
# This is a placeholder for a linked node tree.
yield block
return
assert (
block.dna_type.dna_type_id == b"bNodeTree"
), f"Expected bNodeTree, got {block.dna_type.dna_type_id.decode()})"
nodes = block.get_pointer((b"nodes", b"first")) nodes = block.get_pointer((b"nodes", b"first"))
@ -145,7 +152,11 @@ def _expand_generic_idprops(block: blendfile.BlendFileBlock):
def _expand_generic_nodetree_id(block: blendfile.BlendFileBlock): def _expand_generic_nodetree_id(block: blendfile.BlendFileBlock):
block_ntree = block.get_pointer(b"nodetree", None) if block.bfile.header.version >= 500 and block.bfile.file_subversion >= 4:
# Introduced in Blender 5.0, commit bd61e69be5a7c96f1e5da1c86aafc17b839e049f
block_ntree = block.get_pointer(b"compositing_node_group", None)
else:
block_ntree = block.get_pointer(b"nodetree", None)
if block_ntree is not None: if block_ntree is not None:
yield from _expand_generic_nodetree(block_ntree) yield from _expand_generic_nodetree(block_ntree)

View File

@ -65,6 +65,13 @@ class BlockIterator:
self.progress_cb = progress.Callback() self.progress_cb = progress.Callback()
def open_blendfile(self, bfilepath: pathlib.Path) -> blendfile.BlendFile:
"""Open a blend file, sending notification about this to the progress callback."""
log.info("opening: %s", bfilepath)
self.progress_cb.trace_blendfile(bfilepath)
return blendfile.open_cached(bfilepath)
def iter_blocks( def iter_blocks(
self, self,
bfile: blendfile.BlendFile, bfile: blendfile.BlendFile,
@ -72,7 +79,6 @@ class BlockIterator:
) -> typing.Iterator[blendfile.BlendFileBlock]: ) -> typing.Iterator[blendfile.BlendFileBlock]:
"""Expand blocks with dependencies from other libraries.""" """Expand blocks with dependencies from other libraries."""
self.progress_cb.trace_blendfile(bfile.filepath)
log.info("inspecting: %s", bfile.filepath) log.info("inspecting: %s", bfile.filepath)
if limit_to: if limit_to:
self._queue_named_blocks(bfile, limit_to) self._queue_named_blocks(bfile, limit_to)
@ -127,7 +133,7 @@ class BlockIterator:
continue continue
log.debug("Expanding %d blocks in %s", len(idblocks), lib_path) log.debug("Expanding %d blocks in %s", len(idblocks), lib_path)
libfile = blendfile.open_cached(lib_path) libfile = self.open_blendfile(lib_path)
yield from self.iter_blocks(libfile, idblocks) yield from self.iter_blocks(libfile, idblocks)
def _queue_all_blocks(self, bfile: blendfile.BlendFile): def _queue_all_blocks(self, bfile: blendfile.BlendFile):

View File

@ -56,7 +56,10 @@ def expand_sequence(path: pathlib.Path) -> typing.Iterator[pathlib.Path]:
raise DoesNotExist(path) raise DoesNotExist(path)
if path.is_dir(): if path.is_dir():
yield path # Explode directory paths into separate files.
for subpath in path.rglob("*"):
if subpath.is_file():
yield subpath
return return
log.debug("expanding file sequence %s", path) log.debug("expanding file sequence %s", path)

View File

@ -22,10 +22,12 @@
The modifier_xxx() functions all yield result.BlockUsage objects for external The modifier_xxx() functions all yield result.BlockUsage objects for external
files used by the modifiers. files used by the modifiers.
""" """
import logging import logging
import typing import typing
from blender_asset_tracer import blendfile, bpathlib, cdefs from blender_asset_tracer import blendfile, bpathlib, cdefs
from blender_asset_tracer.blendfile import iterators
from . import result from . import result
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -278,6 +280,34 @@ def modifier_smoke_sim(
) )
@mod_handler(cdefs.eModifierType_Fluid)
def modifier_fluid(
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
) -> typing.Iterator[result.BlockUsage]:
my_log = log.getChild("modifier_fluid")
domain = modifier.get_pointer(b"domain")
if domain is None:
my_log.debug(
"Modifier %r (%r) has no domain", modifier[b"modifier", b"name"], block_name
)
return
# See fluid_bake_startjob() in physics_fluid.c
path = domain[b"cache_directory"]
path, field = domain.get(b"cache_directory", return_field=True)
log.info(" fluid cache at %s", path)
bpath = bpathlib.BlendPath(path)
yield result.BlockUsage(
domain,
bpath,
path_full_field=field,
is_sequence=True,
block_name=block_name,
)
@mod_handler(cdefs.eModifierType_Cloth) @mod_handler(cdefs.eModifierType_Cloth)
def modifier_cloth( def modifier_cloth(
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
@ -289,3 +319,95 @@ def modifier_cloth(
yield from _walk_point_cache( yield from _walk_point_cache(
ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT
) )
@mod_handler(cdefs.eModifierType_DynamicPaint)
def modifier_dynamic_paint(
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
) -> typing.Iterator[result.BlockUsage]:
my_log = log.getChild("modifier_dynamic_paint")
canvas_settings = modifier.get_pointer(b"canvas")
if canvas_settings is None:
my_log.debug(
"Modifier %r (%r) has no canvas_settings",
modifier[b"modifier", b"name"],
block_name,
)
return
surfaces = canvas_settings.get_pointer((b"surfaces", b"first"))
for surf_idx, surface in enumerate(iterators.listbase(surfaces)):
surface_block_name = block_name + b".canvas_settings.surfaces[%d]" % (surf_idx)
point_cache = surface.get_pointer(b"pointcache")
if point_cache is None:
my_log.debug(
"Surface %r (%r) has no pointcache",
surface[b"surface", b"name"],
surface_block_name,
)
continue
yield from _walk_point_cache(
ctx, surface_block_name, modifier.bfile, point_cache, cdefs.PTCACHE_EXT
)
@mod_handler(cdefs.eModifierType_Nodes)
def modifier_nodes(
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
) -> typing.Iterator[result.BlockUsage]:
if not modifier.has_field(b"simulation_bake_directory"):
return
mod_directory_ptr, mod_directory_field = modifier.get(
b"simulation_bake_directory", return_field=True
)
bakes = modifier.get_pointer(b"bakes")
if not bakes:
return
mod_bake_target = modifier.get(b"bake_target")
for bake_idx, bake in enumerate(iterators.dynamic_array(bakes)):
# Check for packed data.
bake_target = bake.get(b"bake_target")
if bake_target == cdefs.NODES_MODIFIER_BAKE_TARGET_INHERIT:
bake_target = mod_bake_target
if bake_target == cdefs.NODES_MODIFIER_BAKE_TARGET_PACKED:
# This data is packed in the blend file, it's not a dependency to trace.
continue
flag = bake.get(b"flag")
use_custom_directory = bool(flag & cdefs.NODES_MODIFIER_BAKE_CUSTOM_PATH)
if use_custom_directory:
bake_directory_ptr, bake_directory_field = bake.get(
b"directory", return_field=True
)
directory_ptr = bake_directory_ptr
field = bake_directory_field
block = bake
else:
directory_ptr = mod_directory_ptr
field = mod_directory_field
block = modifier
if not directory_ptr:
continue
directory = bake.bfile.dereference_pointer(directory_ptr)
if not directory:
continue
bpath = bpathlib.BlendPath(directory.as_bytes_string())
bake_block_name = block_name + b".bakes[%d]" % bake_idx
yield result.BlockUsage(
block,
bpath,
block_name=bake_block_name,
path_full_field=field,
is_sequence=True,
)

View File

@ -21,6 +21,7 @@ import functools
import logging import logging
import pathlib import pathlib
import typing import typing
from typing import Optional
from blender_asset_tracer import blendfile, bpathlib from blender_asset_tracer import blendfile, bpathlib
from blender_asset_tracer.blendfile import dna from blender_asset_tracer.blendfile import dna
@ -57,9 +58,9 @@ class BlockUsage:
block: blendfile.BlendFileBlock, block: blendfile.BlendFileBlock,
asset_path: bpathlib.BlendPath, asset_path: bpathlib.BlendPath,
is_sequence: bool = False, is_sequence: bool = False,
path_full_field: dna.Field = None, path_full_field: Optional[dna.Field] = None,
path_dir_field: dna.Field = None, path_dir_field: Optional[dna.Field] = None,
path_base_field: dna.Field = None, path_base_field: Optional[dna.Field] = None,
block_name: bytes = b"", block_name: bytes = b"",
) -> None: ) -> None:
if block_name: if block_name:

29
cli.py Normal file
View File

@ -0,0 +1,29 @@
# ##### 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.
#
# Copyright (C) 2014-2018 Blender Foundation
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import blender_asset_tracer.cli
if __name__ == "__main__":
print("\n\n *** Running {} *** \n".format(__file__))
blender_asset_tracer.cli.cli_main()

View File

@ -24,9 +24,9 @@ copyright = '2018, Sybren A. Stüvel'
author = 'Sybren A. Stüvel' author = 'Sybren A. Stüvel'
# The short X.Y version # The short X.Y version
version = '1.13' version = '1.21'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '1.13' release = '1.21'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------

2679
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,16 @@
[tool.poetry] [tool.poetry]
name = "blender-asset-tracer" name = "blender-asset-tracer"
version = "1.13" version = "1.21"
homepage = 'https://developer.blender.org/project/profile/79/' homepage = 'https://developer.blender.org/project/profile/79/'
description = "BAT parses Blend files and produces dependency information. After installation run `bat --help`" description = "BAT parses Blend files and produces dependency information. After installation run `bat --help`"
readme = "README.md"
authors = [ authors = [
"Sybren A. Stüvel <sybren@stuvel.eu>", "Sybren A. Stüvel <sybren@blender.org>",
"Campbell Barton", "Campbell Barton",
"At Mind B.V. - Jeroen Bakker", "At Mind B.V. - Jeroen Bakker",
] ]
license = "GPL-2.0+" license = "GPL-2.0+"
classifiers = [ classifiers = [
'Environment :: Console', 'Environment :: Console',
@ -22,29 +24,29 @@ s3 = ["boto3"]
zstandard = ["zstandard"] zstandard = ["zstandard"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.7" python = ">=3.11,<4.0"
requests = "^2.11" requests = "^2.11"
# For S3 storage support: # For S3 storage support:
boto3 = { version = "^1.9", optional = true } boto3 = { version = "^1.9", optional = true }
# For Blender 3.0+ compressed file support. # For Blender 3.0+ compressed file support.
zstandard = { version = "^0.15", optional = true } zstandard = { version = "^0.16", optional = true }
[tool.poetry.dev-dependencies] [tool.poetry.group.dev.dependencies]
mypy = ">=0.942" mypy = ">=0.942"
pytest = "^6.2" pytest = "^7.4.4"
pytest-cov = "^3.0.0" pytest-cov = "^4.1.0"
twine = "^5.0.0"
# for the 'radon cc' command # for the 'radon cc' command
radon = "^3.0" radon = "^3.0"
# for converting profiler output to KCacheGrind input # for converting profiler output to KCacheGrind input
"pyprof2calltree" = "*" "pyprof2calltree" = "*"
# For building documentation # For building documentation
sphinx = "^2.1" sphinx = "^7.2"
sphinx-autobuild = "^0.7" sphinx-autobuild = "^2024.3"
sphinx-rtd-theme = "^0.4" sphinx-rtd-theme = "^2.0"
responses = "^0.10" responses = "^0.10"
pathlib2 = {version = "^2.3", python = "<3.6"}
tox = "^3.12" tox = "^3.12"
types-requests = "^2.25.0" types-requests = "^2.25.0"
@ -53,5 +55,5 @@ bat = 'blender_asset_tracer.cli:cli_main'
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.masonry.api" build-backend = "poetry.core.masonry.api"

View File

@ -1,12 +1,12 @@
[tool:pytest] [tool:pytest]
addopts = -v --cov blender_asset_tracer --cov-report term-missing addopts = -p pytest_cov -v --cov blender_asset_tracer --cov-report term-missing
[pep8] [pep8]
max-line-length = 100 max-line-length = 100
[mypy] [mypy]
# This should match pyproject.toml # This should match pyproject.toml
python_version = 3.7 python_version = 3.11
warn_redundant_casts = True warn_redundant_casts = True
ignore_missing_imports = True ignore_missing_imports = True

View File

@ -20,6 +20,7 @@
import logging import logging
import pathlib import pathlib
import unittest import unittest
from typing import Optional
from blender_asset_tracer import blendfile from blender_asset_tracer import blendfile
@ -29,6 +30,9 @@ logging.basicConfig(
class AbstractBlendFileTest(unittest.TestCase): class AbstractBlendFileTest(unittest.TestCase):
blendfiles: pathlib.Path
bf: Optional[blendfile.BlendFile]
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.blendfiles = pathlib.Path(__file__).with_name("blendfiles") cls.blendfiles = pathlib.Path(__file__).with_name("blendfiles")

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1 @@
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}

View File

@ -0,0 +1,6 @@
[InternetShortcut]
URL=https://leomoon.com/store/shaders/ies-lights-pack/
IDList=
HotKey=0
[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,11

View File

@ -0,0 +1,272 @@
IESNA:LM-63-2002
[TEST]204695
[TESTLAB] UL Verification Services
[ISSUEDATE] 5/24/2013
[MANUFAC] LeoMoon Studios
[LUMCAT]DD42LED
[LUMINAIRE]n/a
[LAMP]n/a
[LAMPCAT]n/a. LUMINAIRE OUTPUT: 3488 Lms.
[OTHER]120.0V 0.3354A 39.72W PF= 0.987
TILT=NONE
1
-1
1
73
30
1
1
-0.75
-0.75
0.19
1
1
39.7
0 2.5 5 7.5 10 12.5 15 17.5 20 22.5 25 27.5 30 32.5 35 37.5
40 42.5 45 47.5 50 52.5 55 57.5 60 62.5 65 67.5 70 72.5 75
77.5 80 82.5 85 87.5 90 92.5 95 97.5 100 102.5 105 107.5 110
112.5 115 117.5 120 122.5 125 127.5 130 132.5 135 137.5 140
142.5 145 147.5 150 152.5 155 157.5 160 162.5 165 167.5 170
172.5 175 177.5 180
0 5 15 25 35 45 55 60 62.5 65 67.5 70 72.5 75 77.5 80 82.5
85 87.5 90 95 105 115 125 135 145 155 165 175 180
433.6 443.7 458.4 469.4 457.0 452.9 477.1 521.8 569.9 618.0
668.4 700.8 722.5 749.0 786.5 822.8 837.3 815.1 769.3 717.5
683.1 649.8 628.7 620.6 591.7 580.1 587.0 757.1 681.6 436.5
313.2 213.9 138.4 75.4 47.1 31.7 19.1 17.3 17.4 21.2
27.9 34.1 39.3 44.1 48.2 53.0 57.7 62.7 68.8 76.9
88.0 88.7 61.6 29.1 11.8 7.6 5.9 4.8 3.7 2.5
1.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.5 457.2 470.0 457.1 453.7 477.0 523.0 570.5 619.4
669.6 705.0 727.5 749.5 782.8 816.2 828.4 803.8 754.9 702.7
669.2 637.7 615.8 606.1 584.8 570.6 587.7 773.7 671.8 445.5
317.0 217.8 141.9 77.0 47.5 31.8 19.5 17.2 17.4 21.5
28.0 34.0 39.3 44.0 48.6 52.9 57.6 62.9 68.9 76.5
87.6 88.1 61.5 29.4 11.6 7.6 5.7 5.0 3.9 2.7
1.6 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 442.5 455.1 468.5 456.3 453.4 476.1 521.8 573.3 624.8
675.7 714.0 735.1 751.8 777.8 802.5 809.2 785.8 742.2 693.8
660.8 630.1 607.0 589.3 568.7 550.9 573.1 778.4 676.9 443.1
319.8 219.4 142.3 75.8 46.6 31.1 19.2 16.9 17.2 21.0
28.0 34.2 39.9 44.7 49.2 53.6 58.4 63.8 69.1 76.3
86.8 87.9 61.8 29.5 11.7 7.4 5.7 4.6 3.6 2.7
1.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 441.1 456.3 468.5 458.0 453.0 473.2 519.3 571.8 625.2
679.2 720.6 741.7 756.0 781.0 805.8 813.5 789.2 750.8 710.0
684.7 663.0 645.7 632.4 605.0 595.5 596.4 839.2 766.4 487.4
348.2 239.8 153.6 81.8 48.9 32.3 19.8 17.0 17.0 21.3
28.2 34.3 40.2 45.1 49.7 54.0 58.5 64.9 71.8 80.2
91.8 90.8 63.2 30.2 12.4 7.7 6.0 5.0 4.1 2.8
1.6 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 439.8 453.7 467.5 457.9 451.6 470.8 516.3 565.8 617.4
672.6 717.5 744.8 767.4 796.8 819.4 824.9 803.9 769.3 737.1
718.4 698.2 671.9 645.7 610.5 595.6 596.0 815.5 728.0 475.2
345.2 238.1 154.2 85.8 50.9 33.2 19.6 16.7 17.0 21.5
28.7 35.1 41.0 45.7 50.2 54.8 59.1 64.8 72.4 81.2
93.4 97.6 70.5 33.4 12.9 7.8 6.0 5.2 4.0 3.0
1.4 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 438.6 452.5 466.4 457.5 451.2 468.6 511.2 559.8 614.0
671.2 720.4 752.5 780.3 810.8 833.5 838.3 816.2 781.9 746.9
728.4 710.2 689.1 657.4 627.7 608.7 602.5 833.0 733.3 490.0
346.0 239.2 154.9 86.9 50.3 32.9 19.3 16.2 16.8 21.2
28.5 35.4 41.6 46.6 51.2 55.8 60.2 65.8 73.1 82.8
95.8 99.8 74.5 36.9 13.9 7.9 6.1 4.8 4.1 3.1
1.6 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 437.0 452.1 465.2 456.0 450.0 465.8 507.8 555.2 608.8
665.3 713.7 744.7 770.6 798.5 819.2 825.0 804.5 774.2 745.2
725.2 701.4 673.7 638.5 603.7 587.1 600.8 808.8 720.5 468.2
338.2 235.4 154.6 87.7 48.8 31.9 18.8 15.8 16.7 20.8
28.2 34.9 40.8 45.8 50.1 54.9 59.2 65.0 72.2 81.4
93.9 98.9 74.2 35.7 13.4 7.8 5.8 5.1 4.0 2.9
1.7 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.4 451.1 465.2 456.5 450.0 466.0 506.5 553.7 605.7
661.2 707.2 737.2 764.7 792.4 812.9 817.0 796.2 763.2 730.8
705.7 677.7 652.6 626.1 597.7 576.7 601.0 827.3 741.5 479.6
346.2 242.5 159.9 91.2 50.2 32.7 19.2 15.8 16.4 21.0
27.7 34.3 40.3 45.5 49.9 54.4 58.8 64.5 71.7 80.3
93.4 98.6 73.1 35.2 13.3 7.9 5.8 4.8 4.1 2.8
1.8 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.4 450.6 464.5 457.2 450.2 466.8 506.6 552.9 603.8
659.0 704.6 735.9 762.8 792.0 812.5 817.0 795.8 761.2 726.5
699.8 672.3 649.1 627.5 598.1 577.7 613.8 848.0 758.8 489.5
356.1 247.1 162.2 92.2 50.8 33.0 19.2 15.9 16.4 20.9
27.6 34.3 40.3 45.4 49.8 54.2 58.9 64.7 71.8 80.1
92.9 97.8 72.7 34.8 13.3 7.8 6.2 4.7 4.1 3.2
1.8 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.3 450.5 464.7 456.8 450.2 465.7 506.5 552.4 603.4
657.0 702.0 734.7 761.0 790.3 812.2 818.0 796.0 760.6 725.6
698.8 670.9 650.5 628.5 598.3 578.5 618.5 863.5 752.8 493.6
362.1 247.5 161.4 92.4 50.8 33.2 18.9 15.8 16.4 20.8
27.7 34.0 40.1 44.9 49.5 54.0 58.7 64.5 71.9 80.4
92.9 97.4 72.4 34.7 13.1 7.9 6.0 4.8 4.0 2.8
1.7 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.0 450.3 464.5 456.7 450.5 466.4 504.6 552.2 602.8
655.1 700.8 733.3 760.0 790.2 813.2 817.6 794.2 759.9 724.0
699.1 671.9 651.3 629.3 596.5 577.3 614.0 858.6 740.6 491.2
365.0 254.9 167.9 95.4 51.4 33.3 19.3 15.8 16.5 21.0
27.4 34.2 39.8 44.8 49.2 53.7 58.2 64.0 71.6 80.1
93.0 97.0 72.2 34.8 13.0 7.9 5.8 5.1 3.9 3.0
1.8 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.0 450.0 464.1 457.2 450.3 467.2 505.2 552.4 600.3
653.4 698.9 731.5 758.7 789.2 812.2 816.5 793.2 760.7 726.4
703.7 675.5 652.5 625.7 592.1 575.5 614.0 853.4 726.6 482.5
362.8 253.4 167.1 95.7 51.7 33.4 18.9 15.8 16.5 20.8
27.5 33.9 39.7 44.3 49.0 53.2 57.8 63.9 70.8 79.1
92.2 97.2 72.1 34.8 13.1 7.6 5.8 4.8 3.9 2.7
1.4 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.8 450.1 464.2 456.6 451.1 466.9 506.1 551.8 599.6
650.5 695.7 728.1 756.2 786.8 810.2 813.9 791.7 762.8 732.0
708.8 681.4 654.8 623.7 587.2 571.0 621.2 856.2 725.8 478.0
352.2 244.4 161.0 93.4 51.8 33.6 19.2 15.8 16.6 20.8
27.7 34.2 39.8 44.6 49.0 53.1 57.7 63.5 70.2 78.2
91.3 97.3 72.6 34.9 13.2 7.8 5.8 4.7 3.8 3.0
1.7 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.0 450.3 464.2 457.9 451.1 467.6 507.1 551.7 598.2
648.9 692.6 725.9 753.9 783.2 806.1 809.2 788.9 762.2 734.7
712.8 685.2 657.8 624.4 585.7 568.1 614.3 864.8 728.5 481.8
351.6 243.9 161.1 93.9 52.0 33.9 19.0 15.6 16.7 20.8
27.5 34.0 39.8 44.3 48.5 53.0 57.3 63.0 69.7 78.0
90.7 96.9 72.4 34.7 13.1 7.7 5.9 5.0 4.1 2.9
1.7 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.7 450.2 464.2 457.7 453.0 468.0 506.7 551.8 597.4
647.9 690.5 723.2 750.5 779.3 800.8 804.2 783.9 759.0 734.1
713.8 687.0 658.5 623.3 585.5 569.8 607.6 852.7 734.6 483.8
356.2 250.5 166.8 95.4 51.8 33.5 19.2 15.9 16.5 21.0
27.8 34.0 39.5 44.4 48.5 52.7 56.8 62.6 69.1 77.8
90.1 96.2 72.4 34.8 12.9 7.7 6.0 5.1 4.2 3.1
1.5 0.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.3 449.9 464.2 457.3 452.4 469.0 508.3 551.6 597.2
646.9 689.5 721.2 746.7 773.8 794.0 796.8 777.8 753.5 729.2
711.0 685.7 657.9 624.0 587.5 571.8 608.2 854.8 727.6 486.9
359.0 252.9 169.6 96.1 52.4 34.0 18.9 15.8 16.7 20.7
27.4 33.8 39.2 44.3 48.0 52.2 56.4 62.2 68.6 77.4
89.6 95.6 72.1 34.5 12.9 7.7 5.8 4.8 3.9 2.8
1.7 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.1 449.5 464.5 458.0 454.0 469.8 507.6 551.6 597.7
646.7 689.1 720.0 744.2 770.2 788.2 789.3 770.3 746.1 723.2
704.8 681.2 656.5 625.3 590.8 573.2 607.7 859.2 718.9 490.9
364.0 253.2 171.0 97.6 53.7 34.3 19.0 15.8 16.6 20.6
27.4 33.5 39.2 43.8 47.7 51.8 56.2 62.0 68.6 76.7
89.1 95.3 71.9 34.3 12.8 7.8 5.8 4.7 3.9 2.6
1.6 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.3 449.2 463.5 457.7 453.6 470.0 508.8 553.0 598.7
648.0 689.8 719.4 741.9 767.7 784.8 784.5 763.5 738.8 717.8
700.7 676.8 654.0 626.0 591.5 569.7 607.1 854.3 710.0 486.0
360.4 251.0 170.6 97.7 53.8 34.3 19.0 16.0 16.4 20.4
27.0 33.3 39.0 43.7 47.8 51.8 56.1 61.6 68.2 76.6
88.8 95.2 71.2 34.0 12.4 7.6 5.9 4.7 3.8 2.9
1.6 0.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.2 449.1 463.1 457.7 454.8 471.5 510.0 554.2 600.1
648.8 690.2 718.3 741.2 766.8 783.2 781.2 759.0 735.5 714.5
698.2 675.1 651.4 622.9 585.8 563.8 597.5 849.9 702.2 474.3
350.1 247.5 167.1 95.9 52.9 34.1 18.8 15.6 16.3 20.4
27.0 33.4 39.1 43.7 47.5 51.7 55.8 61.5 67.9 76.1
88.6 95.2 71.1 34.3 12.8 7.4 5.7 4.7 3.9 2.8
1.5 0.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.1 449.1 463.5 458.2 454.3 472.5 511.2 555.3 601.2
649.8 690.3 717.2 740.0 765.3 781.5 779.2 758.4 734.3 712.2
694.9 671.6 646.5 616.9 576.0 554.0 589.3 847.8 698.8 466.2
345.8 241.9 161.9 93.6 51.8 33.2 18.6 15.6 16.4 20.5
26.9 33.2 38.8 43.5 47.3 51.5 55.8 61.3 68.1 75.9
88.8 94.9 71.2 34.1 12.8 7.6 6.0 4.7 3.9 2.7
1.5 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 434.6 448.9 463.0 458.0 455.5 473.8 513.8 558.3 604.1
651.7 689.8 715.2 740.1 766.2 782.2 782.3 764.2 739.8 713.3
690.4 663.7 635.3 606.5 566.0 545.7 599.2 843.8 708.1 469.0
347.5 246.4 163.8 95.8 52.5 33.9 18.5 15.6 16.2 20.2
26.6 32.6 38.2 42.8 46.8 50.9 55.0 60.8 67.8 76.2
88.8 94.6 70.9 34.3 12.8 7.2 5.6 4.8 3.6 2.6
1.7 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.2 450.5 462.7 458.1 458.5 478.5 520.1 564.6 610.1
656.4 693.8 719.2 742.8 772.8 795.9 804.2 788.2 765.2 742.7
723.4 692.4 658.0 627.9 580.8 560.6 598.6 850.2 703.4 466.2
347.4 247.2 166.7 99.0 55.2 35.0 18.6 15.1 16.3 20.1
26.7 32.9 38.8 43.2 47.1 50.8 55.2 60.8 66.9 74.6
87.4 96.2 74.2 35.4 13.1 7.7 5.8 4.7 3.7 2.8
1.6 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.2 450.0 461.9 459.0 460.5 480.5 523.2 570.1 616.3
661.8 698.2 723.2 744.8 772.0 793.1 796.8 772.2 738.3 708.2
686.9 666.7 648.2 632.6 580.8 562.9 578.9 849.9 752.4 512.4
380.2 272.8 188.9 109.8 59.2 37.0 19.2 15.2 16.0 20.5
26.9 33.2 38.4 42.9 46.5 50.8 55.0 60.8 67.9 76.9
90.2 100.4 77.2 37.0 13.3 7.5 5.7 4.6 3.8 2.5
1.6 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 437.1 451.2 462.1 458.4 460.5 482.5 524.6 573.3 623.8
670.3 706.8 730.2 750.7 779.8 804.1 806.2 776.4 735.8 698.2
677.0 658.0 640.3 622.8 578.0 563.1 591.3 856.8 780.5 513.3
376.1 268.9 184.5 108.2 59.6 37.7 19.2 15.4 15.9 20.7
27.4 33.4 39.0 43.6 47.6 51.3 55.2 61.1 68.9 77.3
91.0 103.1 80.5 38.2 13.5 7.3 5.6 4.4 3.5 2.7
1.5 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 438.5 452.9 462.4 458.0 461.4 483.6 525.8 571.5 621.2
670.5 710.9 738.4 758.8 783.5 806.2 809.0 781.0 737.9 700.2
680.6 664.8 643.0 619.7 588.1 586.3 580.2 854.8 771.8 508.8
370.8 268.5 185.5 108.5 59.1 38.0 19.2 14.9 16.0 20.2
27.1 33.7 39.2 43.9 48.2 51.8 55.2 60.0 67.1 76.4
90.1 102.9 79.4 36.2 12.5 6.8 5.0 4.1 3.3 2.3
1.2 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 440.4 453.6 461.7 456.5 460.4 483.9 523.2 568.4 614.0
662.5 703.5 730.8 753.3 780.3 799.6 796.8 765.7 724.3 688.2
669.7 650.3 627.7 614.5 587.4 581.5 600.7 876.3 802.9 525.2
385.1 282.5 193.6 113.2 61.9 39.6 19.3 14.9 15.7 20.1
26.9 33.3 38.9 42.7 46.0 48.6 50.9 54.8 60.5 68.1
80.9 92.9 69.8 30.8 10.7 6.1 4.5 3.5 2.7 1.8
1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 441.7 455.5 461.8 455.0 457.1 479.2 519.2 563.8 608.8
656.2 696.5 725.8 750.3 780.2 807.2 812.0 784.0 745.7 711.5
691.7 667.2 647.2 637.8 607.0 597.4 601.5 860.3 810.3 537.7
402.4 290.8 200.8 117.8 61.1 38.8 18.9 14.2 15.0 19.0
24.4 29.3 33.0 35.7 37.8 39.7 42.2 46.3 50.7 55.7
66.1 75.6 58.2 27.2 9.3 5.0 3.5 2.5 1.8 1.2
0.5 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.2 457.6 461.9 453.8 455.9 476.6 519.3 563.9 612.0
658.2 693.0 717.5 739.4 770.9 802.3 814.5 792.0 752.7 715.0
690.7 662.6 640.8 634.8 600.0 585.3 589.8 850.9 758.6 490.7
368.4 272.4 191.5 101.1 54.6 35.3 17.5 13.6 13.6 15.1
18.8 21.6 24.7 26.2 27.4 28.6 30.5 34.0 38.2 42.5
50.0 59.1 50.0 24.8 8.0 4.1 2.9 2.0 1.3 0.5
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.7 456.8 461.5 452.2 455.2 476.6 519.3 566.3 615.0
663.2 699.2 722.2 742.8 776.1 808.7 820.4 794.6 748.2 703.3
677.8 654.0 637.6 640.5 611.7 597.5 613.8 895.2 792.1 518.7
388.3 282.3 196.9 106.0 52.8 33.8 17.0 13.2 13.7 13.5
16.5 19.6 22.6 24.1 25.2 26.5 29.2 32.8 36.8 41.1
48.1 56.1 50.9 25.6 8.1 4.1 2.8 2.2 1.3 0.6
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.4 456.7 461.3 453.0 455.4 477.0 518.7 567.3 615.7
662.3 698.7 721.6 741.5 775.0 809.6 826.5 800.7 747.3 695.2
669.2 646.2 629.5 639.8 617.8 599.3 637.2 906.2 791.9 526.3
394.1 282.0 200.1 104.3 56.5 35.3 17.5 13.4 12.9 13.1
16.2 20.6 24.1 24.6 24.6 26.1 28.7 33.0 37.7 42.6
50.2 59.5 52.3 25.8 8.6 4.4 3.0 2.1 1.6 0.9
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0

View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: 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 3 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, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

Binary file not shown.

View File

@ -0,0 +1,6 @@
[InternetShortcut]
URL=https://leomoon.com/store/shaders/ies-lights-pack/
IDList=
HotKey=0
[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,11

View File

@ -0,0 +1,272 @@
IESNA:LM-63-2002
[TEST]204695
[TESTLAB] UL Verification Services
[ISSUEDATE] 5/24/2013
[MANUFAC] LeoMoon Studios
[LUMCAT]DD42LED
[LUMINAIRE]n/a
[LAMP]n/a
[LAMPCAT]n/a. LUMINAIRE OUTPUT: 3488 Lms.
[OTHER]120.0V 0.3354A 39.72W PF= 0.987
TILT=NONE
1
-1
1
73
30
1
1
-0.75
-0.75
0.19
1
1
39.7
0 2.5 5 7.5 10 12.5 15 17.5 20 22.5 25 27.5 30 32.5 35 37.5
40 42.5 45 47.5 50 52.5 55 57.5 60 62.5 65 67.5 70 72.5 75
77.5 80 82.5 85 87.5 90 92.5 95 97.5 100 102.5 105 107.5 110
112.5 115 117.5 120 122.5 125 127.5 130 132.5 135 137.5 140
142.5 145 147.5 150 152.5 155 157.5 160 162.5 165 167.5 170
172.5 175 177.5 180
0 5 15 25 35 45 55 60 62.5 65 67.5 70 72.5 75 77.5 80 82.5
85 87.5 90 95 105 115 125 135 145 155 165 175 180
433.6 443.7 458.4 469.4 457.0 452.9 477.1 521.8 569.9 618.0
668.4 700.8 722.5 749.0 786.5 822.8 837.3 815.1 769.3 717.5
683.1 649.8 628.7 620.6 591.7 580.1 587.0 757.1 681.6 436.5
313.2 213.9 138.4 75.4 47.1 31.7 19.1 17.3 17.4 21.2
27.9 34.1 39.3 44.1 48.2 53.0 57.7 62.7 68.8 76.9
88.0 88.7 61.6 29.1 11.8 7.6 5.9 4.8 3.7 2.5
1.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.5 457.2 470.0 457.1 453.7 477.0 523.0 570.5 619.4
669.6 705.0 727.5 749.5 782.8 816.2 828.4 803.8 754.9 702.7
669.2 637.7 615.8 606.1 584.8 570.6 587.7 773.7 671.8 445.5
317.0 217.8 141.9 77.0 47.5 31.8 19.5 17.2 17.4 21.5
28.0 34.0 39.3 44.0 48.6 52.9 57.6 62.9 68.9 76.5
87.6 88.1 61.5 29.4 11.6 7.6 5.7 5.0 3.9 2.7
1.6 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 442.5 455.1 468.5 456.3 453.4 476.1 521.8 573.3 624.8
675.7 714.0 735.1 751.8 777.8 802.5 809.2 785.8 742.2 693.8
660.8 630.1 607.0 589.3 568.7 550.9 573.1 778.4 676.9 443.1
319.8 219.4 142.3 75.8 46.6 31.1 19.2 16.9 17.2 21.0
28.0 34.2 39.9 44.7 49.2 53.6 58.4 63.8 69.1 76.3
86.8 87.9 61.8 29.5 11.7 7.4 5.7 4.6 3.6 2.7
1.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 441.1 456.3 468.5 458.0 453.0 473.2 519.3 571.8 625.2
679.2 720.6 741.7 756.0 781.0 805.8 813.5 789.2 750.8 710.0
684.7 663.0 645.7 632.4 605.0 595.5 596.4 839.2 766.4 487.4
348.2 239.8 153.6 81.8 48.9 32.3 19.8 17.0 17.0 21.3
28.2 34.3 40.2 45.1 49.7 54.0 58.5 64.9 71.8 80.2
91.8 90.8 63.2 30.2 12.4 7.7 6.0 5.0 4.1 2.8
1.6 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 439.8 453.7 467.5 457.9 451.6 470.8 516.3 565.8 617.4
672.6 717.5 744.8 767.4 796.8 819.4 824.9 803.9 769.3 737.1
718.4 698.2 671.9 645.7 610.5 595.6 596.0 815.5 728.0 475.2
345.2 238.1 154.2 85.8 50.9 33.2 19.6 16.7 17.0 21.5
28.7 35.1 41.0 45.7 50.2 54.8 59.1 64.8 72.4 81.2
93.4 97.6 70.5 33.4 12.9 7.8 6.0 5.2 4.0 3.0
1.4 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 438.6 452.5 466.4 457.5 451.2 468.6 511.2 559.8 614.0
671.2 720.4 752.5 780.3 810.8 833.5 838.3 816.2 781.9 746.9
728.4 710.2 689.1 657.4 627.7 608.7 602.5 833.0 733.3 490.0
346.0 239.2 154.9 86.9 50.3 32.9 19.3 16.2 16.8 21.2
28.5 35.4 41.6 46.6 51.2 55.8 60.2 65.8 73.1 82.8
95.8 99.8 74.5 36.9 13.9 7.9 6.1 4.8 4.1 3.1
1.6 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 437.0 452.1 465.2 456.0 450.0 465.8 507.8 555.2 608.8
665.3 713.7 744.7 770.6 798.5 819.2 825.0 804.5 774.2 745.2
725.2 701.4 673.7 638.5 603.7 587.1 600.8 808.8 720.5 468.2
338.2 235.4 154.6 87.7 48.8 31.9 18.8 15.8 16.7 20.8
28.2 34.9 40.8 45.8 50.1 54.9 59.2 65.0 72.2 81.4
93.9 98.9 74.2 35.7 13.4 7.8 5.8 5.1 4.0 2.9
1.7 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.4 451.1 465.2 456.5 450.0 466.0 506.5 553.7 605.7
661.2 707.2 737.2 764.7 792.4 812.9 817.0 796.2 763.2 730.8
705.7 677.7 652.6 626.1 597.7 576.7 601.0 827.3 741.5 479.6
346.2 242.5 159.9 91.2 50.2 32.7 19.2 15.8 16.4 21.0
27.7 34.3 40.3 45.5 49.9 54.4 58.8 64.5 71.7 80.3
93.4 98.6 73.1 35.2 13.3 7.9 5.8 4.8 4.1 2.8
1.8 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.4 450.6 464.5 457.2 450.2 466.8 506.6 552.9 603.8
659.0 704.6 735.9 762.8 792.0 812.5 817.0 795.8 761.2 726.5
699.8 672.3 649.1 627.5 598.1 577.7 613.8 848.0 758.8 489.5
356.1 247.1 162.2 92.2 50.8 33.0 19.2 15.9 16.4 20.9
27.6 34.3 40.3 45.4 49.8 54.2 58.9 64.7 71.8 80.1
92.9 97.8 72.7 34.8 13.3 7.8 6.2 4.7 4.1 3.2
1.8 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.3 450.5 464.7 456.8 450.2 465.7 506.5 552.4 603.4
657.0 702.0 734.7 761.0 790.3 812.2 818.0 796.0 760.6 725.6
698.8 670.9 650.5 628.5 598.3 578.5 618.5 863.5 752.8 493.6
362.1 247.5 161.4 92.4 50.8 33.2 18.9 15.8 16.4 20.8
27.7 34.0 40.1 44.9 49.5 54.0 58.7 64.5 71.9 80.4
92.9 97.4 72.4 34.7 13.1 7.9 6.0 4.8 4.0 2.8
1.7 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.0 450.3 464.5 456.7 450.5 466.4 504.6 552.2 602.8
655.1 700.8 733.3 760.0 790.2 813.2 817.6 794.2 759.9 724.0
699.1 671.9 651.3 629.3 596.5 577.3 614.0 858.6 740.6 491.2
365.0 254.9 167.9 95.4 51.4 33.3 19.3 15.8 16.5 21.0
27.4 34.2 39.8 44.8 49.2 53.7 58.2 64.0 71.6 80.1
93.0 97.0 72.2 34.8 13.0 7.9 5.8 5.1 3.9 3.0
1.8 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.0 450.0 464.1 457.2 450.3 467.2 505.2 552.4 600.3
653.4 698.9 731.5 758.7 789.2 812.2 816.5 793.2 760.7 726.4
703.7 675.5 652.5 625.7 592.1 575.5 614.0 853.4 726.6 482.5
362.8 253.4 167.1 95.7 51.7 33.4 18.9 15.8 16.5 20.8
27.5 33.9 39.7 44.3 49.0 53.2 57.8 63.9 70.8 79.1
92.2 97.2 72.1 34.8 13.1 7.6 5.8 4.8 3.9 2.7
1.4 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.8 450.1 464.2 456.6 451.1 466.9 506.1 551.8 599.6
650.5 695.7 728.1 756.2 786.8 810.2 813.9 791.7 762.8 732.0
708.8 681.4 654.8 623.7 587.2 571.0 621.2 856.2 725.8 478.0
352.2 244.4 161.0 93.4 51.8 33.6 19.2 15.8 16.6 20.8
27.7 34.2 39.8 44.6 49.0 53.1 57.7 63.5 70.2 78.2
91.3 97.3 72.6 34.9 13.2 7.8 5.8 4.7 3.8 3.0
1.7 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.0 450.3 464.2 457.9 451.1 467.6 507.1 551.7 598.2
648.9 692.6 725.9 753.9 783.2 806.1 809.2 788.9 762.2 734.7
712.8 685.2 657.8 624.4 585.7 568.1 614.3 864.8 728.5 481.8
351.6 243.9 161.1 93.9 52.0 33.9 19.0 15.6 16.7 20.8
27.5 34.0 39.8 44.3 48.5 53.0 57.3 63.0 69.7 78.0
90.7 96.9 72.4 34.7 13.1 7.7 5.9 5.0 4.1 2.9
1.7 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.7 450.2 464.2 457.7 453.0 468.0 506.7 551.8 597.4
647.9 690.5 723.2 750.5 779.3 800.8 804.2 783.9 759.0 734.1
713.8 687.0 658.5 623.3 585.5 569.8 607.6 852.7 734.6 483.8
356.2 250.5 166.8 95.4 51.8 33.5 19.2 15.9 16.5 21.0
27.8 34.0 39.5 44.4 48.5 52.7 56.8 62.6 69.1 77.8
90.1 96.2 72.4 34.8 12.9 7.7 6.0 5.1 4.2 3.1
1.5 0.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.3 449.9 464.2 457.3 452.4 469.0 508.3 551.6 597.2
646.9 689.5 721.2 746.7 773.8 794.0 796.8 777.8 753.5 729.2
711.0 685.7 657.9 624.0 587.5 571.8 608.2 854.8 727.6 486.9
359.0 252.9 169.6 96.1 52.4 34.0 18.9 15.8 16.7 20.7
27.4 33.8 39.2 44.3 48.0 52.2 56.4 62.2 68.6 77.4
89.6 95.6 72.1 34.5 12.9 7.7 5.8 4.8 3.9 2.8
1.7 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.1 449.5 464.5 458.0 454.0 469.8 507.6 551.6 597.7
646.7 689.1 720.0 744.2 770.2 788.2 789.3 770.3 746.1 723.2
704.8 681.2 656.5 625.3 590.8 573.2 607.7 859.2 718.9 490.9
364.0 253.2 171.0 97.6 53.7 34.3 19.0 15.8 16.6 20.6
27.4 33.5 39.2 43.8 47.7 51.8 56.2 62.0 68.6 76.7
89.1 95.3 71.9 34.3 12.8 7.8 5.8 4.7 3.9 2.6
1.6 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.3 449.2 463.5 457.7 453.6 470.0 508.8 553.0 598.7
648.0 689.8 719.4 741.9 767.7 784.8 784.5 763.5 738.8 717.8
700.7 676.8 654.0 626.0 591.5 569.7 607.1 854.3 710.0 486.0
360.4 251.0 170.6 97.7 53.8 34.3 19.0 16.0 16.4 20.4
27.0 33.3 39.0 43.7 47.8 51.8 56.1 61.6 68.2 76.6
88.8 95.2 71.2 34.0 12.4 7.6 5.9 4.7 3.8 2.9
1.6 0.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.2 449.1 463.1 457.7 454.8 471.5 510.0 554.2 600.1
648.8 690.2 718.3 741.2 766.8 783.2 781.2 759.0 735.5 714.5
698.2 675.1 651.4 622.9 585.8 563.8 597.5 849.9 702.2 474.3
350.1 247.5 167.1 95.9 52.9 34.1 18.8 15.6 16.3 20.4
27.0 33.4 39.1 43.7 47.5 51.7 55.8 61.5 67.9 76.1
88.6 95.2 71.1 34.3 12.8 7.4 5.7 4.7 3.9 2.8
1.5 0.4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.1 449.1 463.5 458.2 454.3 472.5 511.2 555.3 601.2
649.8 690.3 717.2 740.0 765.3 781.5 779.2 758.4 734.3 712.2
694.9 671.6 646.5 616.9 576.0 554.0 589.3 847.8 698.8 466.2
345.8 241.9 161.9 93.6 51.8 33.2 18.6 15.6 16.4 20.5
26.9 33.2 38.8 43.5 47.3 51.5 55.8 61.3 68.1 75.9
88.8 94.9 71.2 34.1 12.8 7.6 6.0 4.7 3.9 2.7
1.5 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 434.6 448.9 463.0 458.0 455.5 473.8 513.8 558.3 604.1
651.7 689.8 715.2 740.1 766.2 782.2 782.3 764.2 739.8 713.3
690.4 663.7 635.3 606.5 566.0 545.7 599.2 843.8 708.1 469.0
347.5 246.4 163.8 95.8 52.5 33.9 18.5 15.6 16.2 20.2
26.6 32.6 38.2 42.8 46.8 50.9 55.0 60.8 67.8 76.2
88.8 94.6 70.9 34.3 12.8 7.2 5.6 4.8 3.6 2.6
1.7 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 435.2 450.5 462.7 458.1 458.5 478.5 520.1 564.6 610.1
656.4 693.8 719.2 742.8 772.8 795.9 804.2 788.2 765.2 742.7
723.4 692.4 658.0 627.9 580.8 560.6 598.6 850.2 703.4 466.2
347.4 247.2 166.7 99.0 55.2 35.0 18.6 15.1 16.3 20.1
26.7 32.9 38.8 43.2 47.1 50.8 55.2 60.8 66.9 74.6
87.4 96.2 74.2 35.4 13.1 7.7 5.8 4.7 3.7 2.8
1.6 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 436.2 450.0 461.9 459.0 460.5 480.5 523.2 570.1 616.3
661.8 698.2 723.2 744.8 772.0 793.1 796.8 772.2 738.3 708.2
686.9 666.7 648.2 632.6 580.8 562.9 578.9 849.9 752.4 512.4
380.2 272.8 188.9 109.8 59.2 37.0 19.2 15.2 16.0 20.5
26.9 33.2 38.4 42.9 46.5 50.8 55.0 60.8 67.9 76.9
90.2 100.4 77.2 37.0 13.3 7.5 5.7 4.6 3.8 2.5
1.6 0.3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 437.1 451.2 462.1 458.4 460.5 482.5 524.6 573.3 623.8
670.3 706.8 730.2 750.7 779.8 804.1 806.2 776.4 735.8 698.2
677.0 658.0 640.3 622.8 578.0 563.1 591.3 856.8 780.5 513.3
376.1 268.9 184.5 108.2 59.6 37.7 19.2 15.4 15.9 20.7
27.4 33.4 39.0 43.6 47.6 51.3 55.2 61.1 68.9 77.3
91.0 103.1 80.5 38.2 13.5 7.3 5.6 4.4 3.5 2.7
1.5 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 438.5 452.9 462.4 458.0 461.4 483.6 525.8 571.5 621.2
670.5 710.9 738.4 758.8 783.5 806.2 809.0 781.0 737.9 700.2
680.6 664.8 643.0 619.7 588.1 586.3 580.2 854.8 771.8 508.8
370.8 268.5 185.5 108.5 59.1 38.0 19.2 14.9 16.0 20.2
27.1 33.7 39.2 43.9 48.2 51.8 55.2 60.0 67.1 76.4
90.1 102.9 79.4 36.2 12.5 6.8 5.0 4.1 3.3 2.3
1.2 0.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 440.4 453.6 461.7 456.5 460.4 483.9 523.2 568.4 614.0
662.5 703.5 730.8 753.3 780.3 799.6 796.8 765.7 724.3 688.2
669.7 650.3 627.7 614.5 587.4 581.5 600.7 876.3 802.9 525.2
385.1 282.5 193.6 113.2 61.9 39.6 19.3 14.9 15.7 20.1
26.9 33.3 38.9 42.7 46.0 48.6 50.9 54.8 60.5 68.1
80.9 92.9 69.8 30.8 10.7 6.1 4.5 3.5 2.7 1.8
1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 441.7 455.5 461.8 455.0 457.1 479.2 519.2 563.8 608.8
656.2 696.5 725.8 750.3 780.2 807.2 812.0 784.0 745.7 711.5
691.7 667.2 647.2 637.8 607.0 597.4 601.5 860.3 810.3 537.7
402.4 290.8 200.8 117.8 61.1 38.8 18.9 14.2 15.0 19.0
24.4 29.3 33.0 35.7 37.8 39.7 42.2 46.3 50.7 55.7
66.1 75.6 58.2 27.2 9.3 5.0 3.5 2.5 1.8 1.2
0.5 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.2 457.6 461.9 453.8 455.9 476.6 519.3 563.9 612.0
658.2 693.0 717.5 739.4 770.9 802.3 814.5 792.0 752.7 715.0
690.7 662.6 640.8 634.8 600.0 585.3 589.8 850.9 758.6 490.7
368.4 272.4 191.5 101.1 54.6 35.3 17.5 13.6 13.6 15.1
18.8 21.6 24.7 26.2 27.4 28.6 30.5 34.0 38.2 42.5
50.0 59.1 50.0 24.8 8.0 4.1 2.9 2.0 1.3 0.5
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.7 456.8 461.5 452.2 455.2 476.6 519.3 566.3 615.0
663.2 699.2 722.2 742.8 776.1 808.7 820.4 794.6 748.2 703.3
677.8 654.0 637.6 640.5 611.7 597.5 613.8 895.2 792.1 518.7
388.3 282.3 196.9 106.0 52.8 33.8 17.0 13.2 13.7 13.5
16.5 19.6 22.6 24.1 25.2 26.5 29.2 32.8 36.8 41.1
48.1 56.1 50.9 25.6 8.1 4.1 2.8 2.2 1.3 0.6
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0
433.6 443.4 456.7 461.3 453.0 455.4 477.0 518.7 567.3 615.7
662.3 698.7 721.6 741.5 775.0 809.6 826.5 800.7 747.3 695.2
669.2 646.2 629.5 639.8 617.8 599.3 637.2 906.2 791.9 526.3
394.1 282.0 200.1 104.3 56.5 35.3 17.5 13.4 12.9 13.1
16.2 20.6 24.1 24.6 24.6 26.1 28.7 33.0 37.7 42.6
50.2 59.5 52.3 25.8 8.6 4.4 3.0 2.1 1.6 0.9
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0

View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: 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 3 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, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

Binary file not shown.

View File

@ -93,6 +93,10 @@ class StructTest(unittest.TestCase):
self.s_ulong = dna.Struct(b"ulong", 8) self.s_ulong = dna.Struct(b"ulong", 8)
self.s_uint64 = dna.Struct(b"uint64_t", 8) self.s_uint64 = dna.Struct(b"uint64_t", 8)
self.s_uint128 = dna.Struct(b"uint128_t", 16) # non-supported type self.s_uint128 = dna.Struct(b"uint128_t", 16) # non-supported type
self.s_substruct = dna.Struct(b"substruct")
self.f_substruct_name = dna.Field(self.s_char, dna.Name(b"name[10]"), 10, 0)
self.s_substruct.append_field(self.f_substruct_name)
self.f_next = dna.Field(self.s, dna.Name(b"*next"), 8, 0) self.f_next = dna.Field(self.s, dna.Name(b"*next"), 8, 0)
self.f_prev = dna.Field(self.s, dna.Name(b"*prev"), 8, 8) self.f_prev = dna.Field(self.s, dna.Name(b"*prev"), 8, 8)
@ -109,6 +113,7 @@ class StructTest(unittest.TestCase):
self.f_testint = dna.Field(self.s_int, dna.Name(b"testint"), 4, 4178) self.f_testint = dna.Field(self.s_int, dna.Name(b"testint"), 4, 4178)
self.f_testfloat = dna.Field(self.s_float, dna.Name(b"testfloat"), 4, 4182) self.f_testfloat = dna.Field(self.s_float, dna.Name(b"testfloat"), 4, 4182)
self.f_testulong = dna.Field(self.s_ulong, dna.Name(b"testulong"), 8, 4186) self.f_testulong = dna.Field(self.s_ulong, dna.Name(b"testulong"), 8, 4186)
self.f_substruct = dna.Field(self.s_substruct, dna.Name(b"testsubstruct"), 10, 4194)
self.s.append_field(self.f_next) self.s.append_field(self.f_next)
self.s.append_field(self.f_prev) self.s.append_field(self.f_prev)
@ -125,6 +130,7 @@ class StructTest(unittest.TestCase):
self.s.append_field(self.f_testint) self.s.append_field(self.f_testint)
self.s.append_field(self.f_testfloat) self.s.append_field(self.f_testfloat)
self.s.append_field(self.f_testulong) self.s.append_field(self.f_testulong)
self.s.append_field(self.f_substruct)
def test_autosize(self): def test_autosize(self):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -263,6 +269,19 @@ class StructTest(unittest.TestCase):
self.assertAlmostEqual(2.79, val[1]) self.assertAlmostEqual(2.79, val[1])
fileobj.seek.assert_called_with(4144, os.SEEK_CUR) fileobj.seek.assert_called_with(4144, os.SEEK_CUR)
def test_struct_field_get_subproperty(self):
fileobj = mock.MagicMock(io.BufferedReader)
fileobj.read.return_value = b"my_name"
_, val = self.s.field_get(
self.FakeHeader(),
fileobj,
(b"testsubstruct", b"name"),
as_str=True,
)
self.assertEqual("my_name", val)
fileobj.seek.assert_called_with(4194, os.SEEK_CUR)
def test_char_field_set(self): def test_char_field_set(self):
fileobj = mock.MagicMock(io.BufferedReader) fileobj = mock.MagicMock(io.BufferedReader)
value = 255 value = 255
@ -344,3 +363,10 @@ class StructTest(unittest.TestCase):
expected = struct.pack(b">f", value) expected = struct.pack(b">f", value)
self.s.field_set(self.FakeHeader(), fileobj, b"testfloat", value) self.s.field_set(self.FakeHeader(), fileobj, b"testfloat", value)
fileobj.write.assert_called_with(expected) fileobj.write.assert_called_with(expected)
def test_struct_field_set_subproperty(self):
fileobj = mock.MagicMock(io.BufferedReader)
expected = b"new_name\x00"
self.s.field_set(self.FakeHeader(), fileobj, (b"testsubstruct", b"name"), "new_name")
fileobj.write.assert_called_with(expected)

View File

@ -13,6 +13,7 @@ class BlendFileBlockTest(AbstractBlendFileTest):
def test_loading(self): def test_loading(self):
self.assertFalse(self.bf.is_compressed) self.assertFalse(self.bf.is_compressed)
self.assertEqual(0, self.bf.header.file_format_version)
def test_some_properties(self): def test_some_properties(self):
ob = self.bf.code_index[b"OB"][0] ob = self.bf.code_index[b"OB"][0]
@ -45,6 +46,12 @@ class BlendFileBlockTest(AbstractBlendFileTest):
mname = mesh.get((b"id", b"name"), as_str=True) mname = mesh.get((b"id", b"name"), as_str=True)
self.assertEqual("MECube³", mname) self.assertEqual("MECube³", mname)
# Try to access different file-block items.
verts_ptr = mesh.get(b"mvert")
verts = self.bf.block_from_addr[verts_ptr]
assert verts.get(b"co") == [-1.0, -1.0, -1.0]
assert verts.get(b"co", array_index=1) == [-1.0, -1.0, 1.0]
def test_get_recursive_iter(self): def test_get_recursive_iter(self):
ob = self.bf.code_index[b"OB"][0] ob = self.bf.code_index[b"OB"][0]
assert isinstance(ob, blendfile.BlendFileBlock) assert isinstance(ob, blendfile.BlendFileBlock)
@ -154,6 +161,23 @@ class BlendFileBlockTest(AbstractBlendFileTest):
self.assertEqual("OBümlaut", ob.id_name.decode()) self.assertEqual("OBümlaut", ob.id_name.decode())
class BlendFileLargeBhead8Test(AbstractBlendFileTest):
def setUp(self):
self.bf = blendfile.BlendFile(self.blendfiles / "basic_file_large_bhead8.blend")
def test_loading(self):
self.assertFalse(self.bf.is_compressed)
self.assertEqual(1, self.bf.header.file_format_version)
def test_some_properties(self):
ob = self.bf.code_index[b"OB"][0]
self.assertEqual("Object", ob.dna_type_name)
# Try high level operation to read the object location.
loc = ob.get(b"loc")
self.assertEqual([2.0, 3.0, 5.0], loc)
class PointerTest(AbstractBlendFileTest): class PointerTest(AbstractBlendFileTest):
def setUp(self): def setUp(self):
self.bf = blendfile.BlendFile(self.blendfiles / "with_sequencer.blend") self.bf = blendfile.BlendFile(self.blendfiles / "with_sequencer.blend")
@ -279,6 +303,29 @@ class ArrayTest(AbstractBlendFileTest):
self.assertEqual(name, tex.id_name) self.assertEqual(name, tex.id_name)
class DynamicArrayTest(AbstractBlendFileTest):
def test_dynamic_array_of_bakes(self):
self.bf = blendfile.BlendFile(self.blendfiles / "multiple_geometry_nodes_bakes.blend")
obj = self.bf.code_index[b"OB"][0]
assert isinstance(obj, blendfile.BlendFileBlock)
modifier = obj.get_pointer((b"modifiers", b"first"))
assert isinstance(modifier, blendfile.BlendFileBlock)
bakes = modifier.get_pointer(b"bakes")
bake_count = bakes.count
self.assertEqual(3, bake_count)
for i, bake in enumerate(blendfile.iterators.dynamic_array(bakes)):
if i == 0:
frame_start = 37
if i == 1:
frame_start = 5
if i == 2:
frame_start = 12
self.assertEqual(frame_start, bake.get(b"frame_start"))
class CompressionRecognitionTest(AbstractBlendFileTest): class CompressionRecognitionTest(AbstractBlendFileTest):
def _find_compression_type(self, filename: str) -> magic_compression.Compression: def _find_compression_type(self, filename: str) -> magic_compression.Compression:
path = self.blendfiles / filename path = self.blendfiles / filename
@ -459,3 +506,15 @@ class BlendFileCacheTest(AbstractBlendFileTest):
self.assertIs(bf, blendfile._cached_bfiles[other]) self.assertIs(bf, blendfile._cached_bfiles[other])
self.assertEqual(str(bf.raw_filepath), bf.fileobj.name) self.assertEqual(str(bf.raw_filepath), bf.fileobj.name)
class BlendFileSubVersionTest(AbstractBlendFileTest):
def test_file_subversion(self) -> None:
self.bf = blendfile.BlendFile(self.blendfiles / "multiple_materials.blend")
self.assertEqual(self.bf.file_subversion, 3)
self.bf = blendfile.BlendFile(
self.blendfiles
/ "compositor_nodes/compositor_nodes_blender500_library.blend"
)
self.assertEqual(self.bf.file_subversion, 36)

View File

@ -39,7 +39,7 @@ class BlendPathTest(unittest.TestCase):
PurePath("C:/some/file.blend"), BlendPath(b"C:/some/file.blend").to_path() PurePath("C:/some/file.blend"), BlendPath(b"C:/some/file.blend").to_path()
) )
self.assertEqual( self.assertEqual(
PurePath("C:/some/file.blend"), BlendPath(br"C:\some\file.blend").to_path() PurePath("C:/some/file.blend"), BlendPath(rb"C:\some\file.blend").to_path()
) )
with mock.patch("sys.getfilesystemencoding") as mock_getfse: with mock.patch("sys.getfilesystemencoding") as mock_getfse:

153
tests/test_pack.py Normal file → Executable file
View File

@ -497,6 +497,13 @@ class PackTest(AbstractPackTest):
else: else:
self.fail(f"Expected to have JPEG files in the BAT pack at {self.tpath}.") self.fail(f"Expected to have JPEG files in the BAT pack at {self.tpath}.")
def test_pack_ies_external(self):
ppath = self.blendfiles / "ies-lamp"
infile = ppath / "ies_scene.blend"
packer = pack.Packer(infile, ppath, self.tpath)
packer.strategise()
packer.execute()
class ProgressTest(AbstractPackTest): class ProgressTest(AbstractPackTest):
def test_strategise(self): def test_strategise(self):
@ -687,6 +694,152 @@ class ProgressTest(AbstractPackTest):
) )
class KeepHierarchyPackTest(AbstractPackTest):
def hierarchy_path(self, filepath) -> Path:
"""Return the keep-hierarchy path for a file: target / strip_root(abs_path)."""
return Path(self.tpath, bpathlib.strip_root(filepath))
def test_strategise_keep_hierarchy_no_rewrite(self):
"""When all deps are in-project with relative paths, no rewriting is needed."""
infile = self.blendfiles / "doubly_linked.blend"
packer = pack.Packer(
infile, self.blendfiles, self.tpath, keep_hierarchy=True
)
packer.strategise()
packed_files = (
"doubly_linked.blend",
"linked_cube.blend",
"basic_file.blend",
"material_textures.blend",
"textures/Bricks/brick_dotted_04-bump.jpg",
"textures/Bricks/brick_dotted_04-color.jpg",
)
for pf in packed_files:
path = self.blendfiles / pf
act = packer._actions[path]
self.assertEqual(
pack.PathAction.KEEP_PATH, act.path_action, "for %s" % pf
)
# In keep_hierarchy mode, paths use strip_root(abs_path) instead of
# relative_to(project).
self.assertEqual(
self.hierarchy_path(path), act.new_path, "for %s" % pf
)
self.assertEqual({}, self.rewrites(packer))
self.assertEqual(len(packed_files), len(packer._actions))
def test_strategise_keep_hierarchy_rewrite(self):
"""Deps outside the project go to target/strip_root(path), not _outside_project/."""
ppath = self.blendfiles / "subdir"
infile = ppath / "doubly_linked_up.blend"
packer = pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True)
packer.strategise()
# The blendfile itself should be at target / strip_root(abs_path)
act = packer._actions[infile]
self.assertEqual(pack.PathAction.KEEP_PATH, act.path_action)
self.assertEqual(self.hierarchy_path(infile), act.new_path)
# External files should NOT be under _outside_project/
external_files = (
"linked_cube.blend",
"basic_file.blend",
"material_textures.blend",
"textures/Bricks/brick_dotted_04-bump.jpg",
"textures/Bricks/brick_dotted_04-color.jpg",
)
for fn in external_files:
path = self.blendfiles / fn
act = packer._actions[path]
self.assertEqual(
pack.PathAction.FIND_NEW_LOCATION, act.path_action, "for %s" % fn
)
# Should be at target / strip_root(abs_path), NOT target/_outside_project/...
expected = self.hierarchy_path(path)
self.assertEqual(
expected,
act.new_path,
f"\nEXPECT: {expected}\nACTUAL: {act.new_path}\nfor {fn}",
)
# There should be no _outside_project in any new_path
for path, action in packer._actions.items():
self.assertNotIn(
"_outside_project",
str(action.new_path),
f"_outside_project should not appear in keep_hierarchy mode for {path}",
)
def test_execute_keep_hierarchy(self):
"""Verify files are copied to correct hierarchy and paths are rewritten."""
ppath = self.blendfiles / "subdir"
infile = ppath / "doubly_linked_up.blend"
with pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True) as packer:
packer.strategise()
packer.execute()
# The blendfile should be at its hierarchy position
packed_blend = self.hierarchy_path(infile)
self.assertTrue(packed_blend.exists(), "Blendfile should be in hierarchy")
# There should be NO _outside_project directory
self.assertFalse(
(self.tpath / "_outside_project").exists(),
"_outside_project should not exist in keep_hierarchy mode",
)
# Dependencies should be at their hierarchy positions
for fn in ("linked_cube.blend", "basic_file.blend", "material_textures.blend"):
dep_path = self.hierarchy_path(self.blendfiles / fn)
self.assertTrue(dep_path.exists(), "%s should be in hierarchy" % fn)
# Verify paths were rewritten correctly in the packed blend file.
# The rewritten paths should be relative from the packed blend to
# the packed dependencies.
bfile = blendfile.open_cached(packed_blend, assert_cached=False)
libs = sorted(bfile.code_index[b"LI"])
# Since keep_hierarchy preserves relative positions, the relative paths
# from subdir/doubly_linked_up.blend to the parent blendfiles should
# be the same as the originals (//../linked_cube.blend etc.)
self.assertEqual(b"LILib", libs[0].id_name)
self.assertEqual(b"//../linked_cube.blend", libs[0][b"name"])
self.assertEqual(b"LILib.002", libs[1].id_name)
self.assertEqual(b"//../material_textures.blend", libs[1][b"name"])
def test_execute_keep_hierarchy_no_touch_origs(self):
"""Original files should not be modified."""
ppath = self.blendfiles / "subdir"
infile = ppath / "doubly_linked_up.blend"
with pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True) as packer:
packer.strategise()
packer.execute()
# The original file shouldn't be touched.
bfile = blendfile.open_cached(infile, assert_cached=False)
libs = sorted(bfile.code_index[b"LI"])
self.assertEqual(b"LILib", libs[0].id_name)
self.assertEqual(b"//../linked_cube.blend", libs[0][b"name"])
self.assertEqual(b"LILib.002", libs[1].id_name)
self.assertEqual(b"//../material_textures.blend", libs[1][b"name"])
def test_keep_hierarchy_output_path(self):
"""output_path should use the full hierarchy path."""
infile = self.blendfiles / "basic_file.blend"
packer = pack.Packer(
infile, self.blendfiles, self.tpath, keep_hierarchy=True
)
packer.strategise()
self.assertEqual(self.hierarchy_path(infile), packer.output_path)
class AbortTest(AbstractPackTest): class AbortTest(AbstractPackTest):
def test_abort_strategise(self): def test_abort_strategise(self):
infile = self.blendfiles / "subdir/doubly_linked_up.blend" infile = self.blendfiles / "subdir/doubly_linked_up.blend"

View File

@ -19,6 +19,7 @@
# (c) 2019, Blender Foundation - Sybren A. Stüvel # (c) 2019, Blender Foundation - Sybren A. Stüvel
import pathlib import pathlib
import platform import platform
from typing import Dict
import responses import responses
@ -29,6 +30,12 @@ httpmock = responses.RequestsMock()
class ShamanTransferTest(AbstractBlendFileTest): class ShamanTransferTest(AbstractBlendFileTest):
test_file1: pathlib.Path
test_file2: pathlib.Path
expected_checksums: Dict[pathlib.Path, str]
file_sizes: Dict[pathlib.Path, int]
packed_names: Dict[pathlib.Path, str]
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()

View File

@ -1,7 +1,9 @@
import collections import collections
import functools
import logging import logging
import sys import sys
import typing import typing
from typing import Optional
from blender_asset_tracer import trace, blendfile from blender_asset_tracer import trace, blendfile
from blender_asset_tracer.blendfile import dna from blender_asset_tracer.blendfile import dna
@ -62,9 +64,10 @@ class AssetHoldingBlocksTest(AbstractTracerTest):
self.assertEqual(965, len(self.bf.blocks)) self.assertEqual(965, len(self.bf.blocks))
self.assertEqual(4, blocks_seen) self.assertEqual(4, blocks_seen)
class DepsTest(AbstractTracerTest): class DepsTest(AbstractTracerTest):
@staticmethod @staticmethod
def field_name(field: dna.Field) -> typing.Optional[str]: def field_name(field: Optional[dna.Field]) -> typing.Optional[str]:
if field is None: if field is None:
return None return None
return field.name.name_full.decode() return field.name.name_full.decode()
@ -87,18 +90,18 @@ class DepsTest(AbstractTracerTest):
exp = expects.get(dep.block_name, None) exp = expects.get(dep.block_name, None)
if isinstance(exp, (set, list)): if isinstance(exp, (set, list)):
self.assertIn(actual, exp, msg="for block %s" % dep.block_name) self.assertIn(actual, exp, msg="for block %r" % dep.block_name)
exp.remove(actual) exp.remove(actual)
if not exp: if not exp:
# Don't leave empty sets in expects. # Don't leave empty sets in expects.
del expects[dep.block_name] del expects[dep.block_name]
elif exp is None: elif exp is None:
self.assertIsNone( self.assertIsNone(
actual, msg="unexpected dependency of block %s" % dep.block_name actual, msg="unexpected dependency of block %r" % dep.block_name
) )
del expects[dep.block_name] del expects[dep.block_name]
else: else:
self.assertEqual(exp, actual, msg="for block %s" % dep.block_name) self.assertEqual(exp, actual, msg="for block %r" % dep.block_name)
del expects[dep.block_name] del expects[dep.block_name]
# All expected uses should have been seen. # All expected uses should have been seen.
@ -191,11 +194,11 @@ class DepsTest(AbstractTracerTest):
def test_seq_image_udim_sequence(self): def test_seq_image_udim_sequence(self):
expects = { expects = {
b"IMcube_UDIM.color": Expect( b"IMcube_UDIM.color": Expect(
'Image', "Image",
'name[1024]', "name[1024]",
None, None,
None, None,
b'//cube_UDIM.color.<UDIM>.png', b"//cube_UDIM.color.<UDIM>.png",
True, True,
), ),
} }
@ -367,6 +370,10 @@ class DepsTest(AbstractTracerTest):
}, },
) )
def test_block_li_packed(self):
# Packed libraries should not be traced.
self.assert_deps("74871-packed-libraries.blend", {})
def test_deps_recursive(self): def test_deps_recursive(self):
self.assert_deps( self.assert_deps(
"doubly_linked.blend", "doubly_linked.blend",
@ -504,6 +511,26 @@ class DepsTest(AbstractTracerTest):
}, },
) )
def test_compositor_nodes(self) -> None:
"""Test compositor node trees.
Since Blender 5.0 these use a different DNA field, and can also be
linked from other files.
"""
self.assert_deps(
"compositor_nodes/compositor_nodes_blender500_workfile.blend",
{
b"LIcompositor_nodes_blender500_library.blend": Expect(
type="Library",
full_field="name[1024]",
dirname_field=None,
basename_field=None,
asset_path=b"//compositor_nodes_blender500_library.blend",
is_sequence=False,
),
},
)
def test_usage_abspath(self): def test_usage_abspath(self):
deps = [ deps = [
dep dep
@ -530,6 +557,56 @@ class DepsTest(AbstractTracerTest):
}, },
) )
def test_geonodes_sim_data(self) -> None:
# Simplify the rest of the code by putting the values that are the same of all cases here:
expect_bake = functools.partial(
Expect,
dirname_field=None,
basename_field=None,
is_sequence=True,
)
expects = {
# Two objects that use "Inherit from Modifer":
b"OBCustom Bake Path.modifiers[0].bakes[0]": [
# Custom path set on the sim node, so this is sim node data.
expect_bake(
type="NodesModifierBake",
full_field="*directory",
asset_path=b"//bakePath",
),
],
b"OBDefault Bake Path.modifiers[0].bakes[0]": [
# NO custom path set on the sim node, so this follows the modifier data.
expect_bake(
type="NodesModifierData",
full_field="*simulation_bake_directory",
asset_path=b"//config-on-sim-node",
),
],
# Two objects that have the config only on the node itself:
b"OBCustom Bake Path.001.modifiers[0].bakes[0]": [
expect_bake(
type="NodesModifierBake",
full_field="*directory",
asset_path=b"//set-on-node",
),
],
b"OBDefault Bake Path.001.modifiers[0].bakes[0]": [
expect_bake(
type="NodesModifierData",
full_field="*simulation_bake_directory",
asset_path=b"//only-set-on-modifier",
),
],
}
# NOTE: there are two more objects in the scene, 'Packed Bake' and
# 'Packed Bake.001'. But, because those use packed data (on the modifier
# resp. bake level), they should not be listed as dependencies.
self.maxDiff = None
self.assert_deps("geometry-nodes-sim/geonodes-sim-cache.blend", expects)
def test_recursion_loop(self): def test_recursion_loop(self):
infinite_bfile = self.blendfiles / "recursive_dependency_1.blend" infinite_bfile = self.blendfiles / "recursive_dependency_1.blend"

View File

@ -24,7 +24,8 @@ class ExpandFileSequenceTest(AbstractBlendFileTest):
path = self.blendfiles / "udim/cube_UDIM.color.<UDIM>.png" path = self.blendfiles / "udim/cube_UDIM.color.<UDIM>.png"
actual = list(file_sequence.expand_sequence(path)) actual = list(file_sequence.expand_sequence(path))
imgseq = [ imgseq = [
self.blendfiles / ("udim/cube_UDIM.color.%04d.png" % num) for num in range(1001, 1004) self.blendfiles / ("udim/cube_UDIM.color.%04d.png" % num)
for num in range(1001, 1004)
] ]
self.assertEqual(imgseq, actual) self.assertEqual(imgseq, actual)

View File

@ -1,6 +1,6 @@
[tox] [tox]
isolated_build = true isolated_build = true
envlist = py37, py38, py39, py310 envlist = py311, py312, py313, py314
[testenv] [testenv]
whitelist_externals = poetry whitelist_externals = poetry

View File

@ -9,12 +9,15 @@ poetry version $1
sed "s/version = '[^']*'/version = '$1'/" -i docs/conf.py sed "s/version = '[^']*'/version = '$1'/" -i docs/conf.py
sed "s/release = '[^']*'/release = '$1'/" -i docs/conf.py sed "s/release = '[^']*'/release = '$1'/" -i docs/conf.py
sed "s/__version__\s*=\s*\"[^']*\"/__version__ = \"$1\"/" -i blender_asset_tracer/__init__.py sed "s/__version__\s*=\s*\"[^']*\"/__version__ = \"$1\"/" -i blender_asset_tracer/__init__.py
sed --posix "s/\(dist\/blender[_-]asset[_-]tracer-\)\([0-9.betalphdv-]*[0-9]\)/\1$1/g" -i README.md
git diff git diff
echo echo
echo "Don't forget to commit and tag:" echo "Don't forget to commit and tag:"
echo git commit -m \'Bumped version to $1\' pyproject.toml blender_asset_tracer/__init__.py docs/conf.py echo git commit -m \'Bumped version to $1\' pyproject.toml blender_asset_tracer/__init__.py docs/conf.py README.md
echo git tag -a v$1 -m \'Tagged version $1\' echo git tag -a v$1 -m \'Tagged version $1\'
echo echo
echo "Build the package & upload to PyPi using:" echo "Build the package & upload to PyPi using:"
echo "poetry build && poetry publish" echo "poetry build"
echo "poetry run twine check dist/blender_asset_tracer-$1.tar.gz dist/blender_asset_tracer-$1-*.whl"
echo "poetry run twine upload -r bat dist/blender_asset_tracer-$1.tar.gz dist/blender_asset_tracer-$1-*.whl"