Compare commits

...

326 Commits

Author SHA1 Message Date
Ian Chua
e4566d32be fix(flatpak): prefetch CPython source archive 2026-07-03 17:22:40 +08:00
SoftFever
0918f9319c rename example plugin 2026-07-03 16:46:28 +08:00
SoftFever
da04681289 rename example plugin 2026-07-03 16:41:54 +08:00
SoftFever
32756f0402 add orca plugin example 2026-07-03 16:30:28 +08:00
SoftFever
6c89dd4e50 expose more assemble api 2026-07-03 16:30:16 +08:00
Ian Chua
c00d284786 Remove 'Open Terminal' button in PluginsDialog HTML. (#14552)
# Description

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-03 15:41:30 +08:00
Andrew
c334d05214 Remove 'Open Terminal' button in PluginsDialog HTML. 2026-07-03 15:40:13 +08:00
SoftFever
6fd1176661 add status bar for plugin/capability status instead of message box 2026-07-03 14:57:35 +08:00
Ian Chua
e3c6ec309d feat: UI for mixed capability state 2026-07-03 14:48:49 +08:00
Ian Chua
ba41b54c4b Fix display issues in post-processing options group (#14549)
# Description

This issus is only on windows.

# Screenshots/Recordings/Graphs

before:

<img width="300" alt="image"
src="https://github.com/user-attachments/assets/1f0dd1bb-fe37-442b-ba75-81c0c895d1f3"
/>

after:

<img width="300" alt="image"
src="https://github.com/user-attachments/assets/92f42ad5-b5c8-415f-8e10-f78d2a3cc45c"
/>

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-03 14:42:42 +08:00
Ian Chua
3668b01721 Merge branch 'main' into feat/plugin-feature 2026-07-03 13:29:40 +08:00
Ian Chua
db8e03dbc7 chore: migrate docs over to wiki 2026-07-03 13:28:42 +08:00
Andrew
e5fd028caa Fix display issues in post-processing options group 2026-07-03 12:38:41 +08:00
raistlin7447
adc8763099 fix: crash in Measure tool when a plain edge is the first selection (#14538)
* fix: crash in Measure tool when a plain edge is the first selection

The SPHERE_2 gripper raycaster called get_feature_offset() on
.first.feature instead of .second.feature (copy-pasted from the SPHERE_1
block). Plain planar-border edges store no extra point, so the Edge
branch dereferenced an empty optional behind a release-stripped assert,
aborting on Flatpak and undefined behavior elsewhere.

Point the SPHERE_2 raycaster at .second.feature and fall the Edge branch
back to the edge midpoint.

Fixes #14018
2026-07-03 11:29:28 +08:00
raistlin7447
036bd7bcec feat: add {first_object_name} filename placeholder (#14497)
{input_filename_base} is meant to be the saved project's file name. Before
#13753 a bug made it fall back to the first object's name when a project was
saved; #13753 fixed it to use the project name. Some users relied on the old
behavior to get the part name into their output file name and had no
placeholder to recover it ({model_name} is the 3mf designer metadata, blank
for plain STL imports).

Add {first_object_name} as a dedicated placeholder for the first printable
object on the current plate, populated in update_object_placeholders()
independently of {input_filename_base}.

Closes #14493
2026-07-03 00:14:32 +08:00
raistlin7447
d24e7f75ef fix: crash when rotating the prime tower (#14499)
Selecting the prime tower and rotating it (PageUp/PageDown) crashed.
Selection::notify_instance_update() indexed m_model->objects with the
wipe tower's synthetic id (>= 1000), which is not a ModelObject index,
so the lookup returned garbage and dereferencing it segfaulted.

do_rotate/do_scale/do_mirror already skip the wipe tower in their own
loops but all call this shared helper, so scale and mirror hit the same
fault. Selection::drop() had the same latent bug via a direct index.
Guard both with the >= 1000 check already used throughout the file.

Fixes #14498
2026-07-03 00:08:15 +08:00
sharanchius
0f88b88f3b Updated and fixed the Lithuanian translation v2.4.1 (#14532)
Atnaujintas lietuvių kalbos vertimas v2.4.1
2026-07-02 23:51:55 +08:00
Noisyfox
464a81d585 Support accessing coFloatsOrPercents values in gcode template (#14526)
* Support accessing `coFloatsOrPercents` values in gcode template (OrcaSlicer/OrcaSlicer#14522)

* Vector option values are separated by comma

* Fix wrong cast used for checking nullability
2026-07-02 23:51:20 +08:00
dependabot[bot]
4c58d0adf8 Bump actions/checkout from 6 to 7 (#14517)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-07-02 23:50:31 +08:00
dependabot[bot]
ce6c2ec7ce Bump actions/cache from 5 to 6 (#14516)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 23:50:06 +08:00
Gabriel Monteiro
dbe99d0d6f feat(log): enable console logging in RelWithDebInfo builds (#14439)
* feat(log): enable console logging in RelWithDebInfo builds

* perf(log): make console logging async to avoid blocking startup

* fix(log): gate console sink to RelWithDebInfo builds

---------

Co-authored-by: Gabriel <bielpaess912@gmail.com>
2026-07-02 21:49:51 +08:00
Ian Chua
25b29c0b53 update docs and comments 2026-07-02 19:34:04 +08:00
ExPikaPaka
5bccc25705 Fix "plug-in file may be in use" install failure when migrating from older versions (#14373) (#14528)
Fix network plug-in install failing when the plug-in DLL is in use (#14373)

Switching or reinstalling the Bambu network plug-in from a running
OrcaSlicer failed with "The plug-in file may be in use". install_plugin()
deleted each existing file before extracting the new one, and on Windows a
currently-loaded DLL (BambuSource.dll, or the legacy networking library)
cannot be removed or overwritten in place, so the whole install aborted.

Rename an in-use file aside to "<name>.old" before writing the new one: the
running module keeps mapping the renamed file while the new version is
extracted, so the install succeeds without having to unload the plug-in
first. Stale ".old" files are cleaned up at the start of on_init_network(),
before the plug-in is (re)loaded, so they do not accumulate.
2026-07-02 18:58:23 +08:00
SoftFever
a6ccbced03 Feat/update preset validator (#14507)
# Description

This PR expands profile validation so we can catch backward
compatibility issues with custom presets generated by older OrcaSlicer
releases. It also adds missing `renamed_from` metadata for presets that
were renamed or moved, so older user presets can resolve their original
parent names against the current system profiles.

## Background

Many users have reported missing preset issues after upgrading past
2.4.1. Investigation showed two common causes:

- preset lookup and compatibility checks did not always account for
`renamed_from`
- some renamed base presets were missing the old preset name in their
`renamed_from` metadata

The existing profile workflow validates the current system profile tree
and a single nightly-generated custom preset bundle. That is useful for
catching current profile errors, but it does not validate user presets
generated by older OrcaSlicer versions against the current system
profiles. As a result, older missing-parent compatibility gaps can slip
through.

## Changes

- Update `check_profiles.yml` to validate historical custom preset
fixtures from `OrcaSlicer/OrcaSlicer-profile-validator`.
- Download the fixture manifest from the public `fixture-archive`
release.
- Validate each `orca_custom_presets_<version>.zip` fixture
independently against the current PR's `resources/profiles`.
- Generate per-version validation logs and upload them as workflow
artifacts.
- Fail profile validation if any historical fixture version fails.
- Add missing `renamed_from` aliases for renamed/moved presets found by
the historical fixture validation.

## Profile Compatibility Fixes

This PR adds aliases for older parent names including:

- `0.20mm Bambu Support W @BBL X1C` -> `0.20mm Standard @BBL X1C`
- `Bambu PLA Impact @BBL X1C` -> `Bambu PLA Impact @System`
- `Ginger Generic rPLA` -> `Ginger Generic PLA`
- `Ginger Generic rPETG` -> `Ginger Generic PETG`
- legacy `Panchroma PLA Stain` BBL filament names -> current `Panchroma
PLA Satin` names
- legacy Elegoo casing/name variants such as `Elegoo RAPID PLA+`,
`Elegoo RAPID PETG`, `Elegoo RAPID PETG+`, and `Elegoo PETG Pro @System`

## Validation Flow

The custom preset validation step now:

1. Downloads `manifest.json` from the `fixture-archive` release.
2. Iterates over every fixture listed in the manifest.
3. Copies the current branch's `resources/profiles` into a temporary
profile tree.
4. Removes any existing `user` directory from that temporary tree.
5. Unzips exactly one historical fixture into the temporary tree.
6. Runs `OrcaSlicer_profile_validator -p <temp profile tree> -l 2`.
7. Writes a version-specific log and a consolidated summary.

This keeps validation scoped per fixture version and avoids mixing
generated user presets from different OrcaSlicer releases.

## Fixture Source

Historical fixtures are stored as public release assets in:

`OrcaSlicer/OrcaSlicer-profile-validator`, release tag `fixture-archive`

Each release asset is expected to be named like:

```text
orca_custom_presets_v2.4.1.zip
```

## Testing

Validated locally with:

- current system profile validation
- BBL filament subtype validation
- historical custom preset fixture validation
- extra profile JSON check in a clean profile tree

The affected historical fixture set passed after adding the missing
`renamed_from` aliases.

The release manifest controls which fixture versions are validated.
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-02 18:47:49 +08:00
SoftFever
c7b28565ef don't upload logs 2026-07-02 18:44:45 +08:00
SoftFever
66b3e27af3 fix "Bambu PLA Impact @BBL X1C" 2026-07-02 18:20:56 +08:00
SoftFever
3bc2c51fe9 revert change in resources\profiles\OrcaFilamentLibrary\filament\Bambu\Bambu PLA Impact @System.json 2026-07-02 17:49:48 +08:00
Ian Chua
ecddf3d18f feat: Python Plugins 2026-07-02 17:49:36 +08:00
SoftFever
187beb68c3 fix voron 2026-07-02 17:42:47 +08:00
Ian Chua
7d62ded630 fix: add renamed_from for missing names from 1.9.0 -> 2.2.0 2026-07-02 11:25:22 +08:00
SoftFever
395e070a0e fix build errors 2026-07-01 23:51:33 +08:00
SoftFever
895488048c Fix issue that Orca keeps asking for re-install Bambu network plugin (#14511)
# Description

There is a bug that if user used to use legacy plugin, the
`NetworkAgent::use_legacy_network` will not be properly reset after user
install a newer plugin version through the plugin update dialog (ie, not
from selecting a plugin version from Preference screen). And in that
case Orca will download the legacy plugin but instead installing it with
a newer version suffix. So on next start up Orca can't find the plugin
because it tries to load using legacy version suffix instead.

This PR fixes it by getting rid of that error-prone static variable,
instead it always use the version number directly.

Fix #14373
Fix #14441

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-01 23:21:09 +08:00
SoftFever
ff556f2867 fix: treat renamed printer presets as compatible aliases (#14504)
# Description

Fixes user filament presets that reference an old printer preset name in
`compatible_printers`, even when the current system printer preset
correctly declares that old name in `renamed_from`.

## Core Issue

`PresetCollection::find_preset()` was updated to resolve `renamed_from`,
but this specific failure does not go through `find_preset()`.

For root user filament presets with empty `inherits`, Orca infers
`compatible_printers` from the filament name suffix after `@`. For
example:

```json
"ELEGOO PLA Base @Elegoo Neptune 4 Pro (0.4 nozzle)"
```

loads with:

```json
"compatible_printers": ["Elegoo Neptune 4 Pro (0.4 nozzle)"]
```

The current system printer preset is named:

```text
Elegoo Neptune 4 Pro 0.4 nozzle
```

and has:

```json
"renamed_from": "Elegoo Neptune 4 Pro (0.4 nozzle)"
```

However, filament compatibility was doing a direct string comparison
against the active printer name. Since that compatibility path does not
call `find_preset()`, the `renamed_from` mapping was never considered.

## Fix

Teach printer compatibility checks to treat
`active_printer.preset.renamed_from` entries as aliases of the active
printer name.

This preserves existing user preset JSON and avoids rewriting
`compatible_printers`, while allowing old printer suffixes to remain
compatible with renamed system printer presets.

## Tests

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-01 21:56:31 +08:00
SoftFever
81546a1b56 Merge branch 'main' into fix/renamed-printer-filament-compat 2026-07-01 21:56:22 +08:00
SoftFever
ec954696b4 fix qidi profiles 2026-07-01 21:55:27 +08:00
SoftFever
f516f47c8e add profile validator checks 2026-07-01 21:55:19 +08:00
Noisyfox
6b886b04f2 Don't keep a separate use_legacy_network which is error-prone. Always infer from installed plugin version. (OrcaSlicer/OrcaSlicer#14441) 2026-07-01 21:17:11 +08:00
Noisyfox
ac79886c5c NetworkAgent::use_legacy_network is only meant to be an intention of whether user wants to use legacy plugin or not, anything that actually interact with the plugin should use the loaded plugin version instead. 2026-07-01 21:03:53 +08:00
Ian Chua
43a83397d4 fix: restore presets that were renamed based on errors logged by workflow 2026-07-01 18:53:36 +08:00
Ian Chua
ced980e6a8 feat: update preset validator to validate against older generated presets 2026-07-01 18:34:07 +08:00
SoftFever
57297d5ab1 keep harmless sla normalize 2026-07-01 17:46:53 +08:00
yw4z
49fe64cb07 Fix: Filament section not foldable on launch for toolchanger / idex printers (#14406)
* init

* Update Plater.cpp
2026-07-01 12:21:40 +03:00
KrishManan
8d84204aeb Add startup progress bar to loading screen (#14155)
* Add startup progress bar to splash screen

* Move progress bar down and remove percentages

* Cleaned up changes

* Update GUI_App.cpp

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-07-01 11:45:25 +03:00
SoftFever
9af07685fd revert changes in Preset.cpp 2026-07-01 15:25:54 +08:00
SoftFever
e39be23d6d fix compatible_prints/printers on load instead 2026-07-01 15:25:43 +08:00
Ian Chua
8ba9630e53 fix 2026-07-01 13:00:18 +08:00
Ian Chua
1a89c18ff2 fix: treat renamed printer presets as compatible aliases 2026-07-01 12:58:17 +08:00
Ian Chua
0541918592 fix: fix type name from preset to process in the preset bundle dialog (#14481)
# Description

The type field for process presets is wrong. It should be process
instead of preset

<img width="2532" height="1576" alt="image"
src="https://github.com/user-attachments/assets/ef73f895-8ce9-4b04-957d-c12cdbaf8f9b"
/>

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-01 11:13:27 +08:00
Ioannis Giannakas
3c8c667dbd Add "First layer fan speed" option (per filament) (#14270)
Implement First layer fan speed
2026-06-30 10:48:58 +01:00
raistlin7447
03568b5200 fix: slicing fails when filament_end_gcode uses layer_z with a wipe tower (#14462)
WipeTowerIntegration::append_tcr processed filament_end_gcode with only
layer_num in its placeholder config, so a filament_end_gcode referencing
{layer_z} could not be evaluated and slicing aborted. This affects any
multi-filament print that routes tool changes through the prime/wipe tower
(for example a support filament on a Bambu printer); the same macro works
in machine_end_gcode and on the non-wipe-tower set_extruder path, which
both define layer_z.

Set layer_z to tcr.print_z, the value this function already provides to its
change_filament_gcode and tcr_rotated_gcode placeholders.

Fixes #10119
2026-06-30 09:12:12 +08:00
raistlin7447
3cf0f4dba5 test(fff_print): re-enable tests disabled by the m_origin clipper throw (#14482)
PR #13712 fixed the uninitialized Print::m_origin (commit 99dea01cc3, "Fix coord
out-of-range exception caused by m_origin memory not initialized to 0") that made
headless slice() intermittently throw ClipperLib's "Coordinate outside allowed
range". With that root cause fixed, the three tests disabled for it pass again,
so drop their [NotWorking] tags.
2026-06-30 09:03:44 +08:00
Rodrigo Faselli
59e655dd1d 💥Crash Fix Object settings table (#14479)
* Crash Fix Object settings

* Update GUI_ObjectTable.cpp
2026-06-30 08:55:09 +08:00
yw4z
19be9604de Preferences Dialog fixes & improvements (Wiki link support) (#13857)
* init

* update

* update

* Update Preferences.cpp

* Update Preferences.cpp

* Update Preferences.cpp

* Update Preferences.cpp

* move filament sync mode next to auto sync

* move auto backup next to load behaviour
2026-06-29 21:23:56 +03:00
Ian Chua
8e868118d2 fix: fix type name from preset to process in the preset bundle dialog 2026-06-29 15:16:11 +08:00
raistlin7447
f13f67959c fix: PA pattern calibration crash with first layer line width 0 (#14447)
CalibPressureAdvancePattern::line_width_first_layer() returned the raw
initial_layer_line_width, so a value of 0 (which means "use the default")
fed 0 into the Flow spacing math and threw FlowErrorNegativeSpacing,
crashing the whole app when slicing a PA pattern calibration.

Mirror the guard already present in the sibling line_width(): when the
configured width is non-positive, fall back to auto_extrusion_width.

Add a libslic3r regression test covering the width resolution.

Fixes #13188
2026-06-29 09:22:23 +08:00
Ian Bassi
ae2c36f091 General Localization minor fix + Spanish Update (#14274) 2026-06-28 13:30:31 -03:00
SoftFever
14e572db5f Add nozzle flow variant & make some options multi-variant (#13712)
# Description

Port BBS per-extruder config variants toggle, thanks Bambu!

Also fix invalid num error when resetting multi-variant filament
overrides.

TODOs:

- [x] Make more configs multi-variant (such as speeds)
- [x] Add flow variant (normal/high flow) combo box
- [x] Add botton to sync config to other variant


# Screenshots/Recordings/Graphs

<img width="846" height="1494" alt="image"
src="https://github.com/user-attachments/assets/47abf053-f5b8-4c86-ac0b-c0323ed8b242"
/>

<img width="1478" height="1189" alt="707420e5be01c30af3e6ede1f3dc9b67"
src="https://github.com/user-attachments/assets/11268712-da65-42ab-9cb8-3cede7790a5c"
/>

<img width="1478" height="1189" alt="81d5d3877f4d5bf9dccd44d7f0b30aa7"
src="https://github.com/user-attachments/assets/cda5ea51-07c3-48fc-bfbb-1428dca14e26"
/>


## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
Fix #10336
2026-06-28 22:30:40 +08:00
Heiko Liebscher
449a4cf9fc Update German (de) translation (#14465)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:37:15 +08:00
SoftFever
943a75af0a Preventing removal of old networking plugins on app version update as Orca allow user to pin the plugin version 2026-06-28 13:26:07 +08:00
yw4z
02bc8969a2 Fix: Missing inherited filaments on vendor profiles cannot found on OrcaFilamentLibrary (#12060)
* init

* ix: resolve OFL filament inherits in printer creation via base_bundle

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-28 13:07:09 +08:00
SoftFever
c2e91cb86c validator: detect ambiguous (duplicate) filament subtypes per printer (#14459)
* validator: detect duplicate filament subtype per printer (opt-in)

A filament is matched from the AMS by (filament_id + printer compatibility);
if two compatible filament presets for one printer share a filament_id, the
match is ambiguous and the runtime silently picks whichever loads first.

Add PresetBundle::check_duplicate_filament_subtypes(), gated behind a new
has_errors(check_duplicate_filament_subtypes) parameter and the validator's
-f/--check_filament_subtypes flag (off by default). For each system printer it
groups its vendor's compatible filament presets by filament_id and errors on
any group of 2+, reporting each preset as a clickable file:// URI with a single
"how to fix" hint. CI runs it for BBL only (-v BBL -f) until the other vendors'
profiles are cleaned up.

* profiles: fix ambiguous BBL filament matches

Resolve the duplicate-filament-subtype errors flagged by the validator:
- align compatible_printers with Bambu Studio where Orca over-claimed a nozzle
  that already has a dedicated preset (Bambu PLA Basic/Matte/ABS @BBL H2DP;
  Bambu ASA/PETG HF @BBL H2DP 0.6 nozzle; Fiberon PETG-ESD @BBL X1)
- fix a copy-pasted printer name in Overture Matte PLA @BBL A1M 0.2 nozzle
- fix a wrong inherits in Panchroma PLA Silk @BBL X1C 0.2 nozzle (was inheriting
  Panchroma PLA @base, giving it filament_id GFPM001 instead of GFPM004)

Bump BBL profile version.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-28 00:42:02 +08:00
Ian Chua
a0846ec215 fix: update .info base_id if it is mismatched or empty (#14430)
* fix: update .info base_id if it is mismatched or empty

* Merge branch 'main' into fix/update-base-id-on-mismatch-or-missing

* early return
2026-06-27 23:07:44 +08:00
Ian Chua
0e8288de36 fix: old preset names should resolved renamed_from field (#14429)
* fix: old preset names should resolved renamed_from field

* chore: remove misleading comments

* Merge branch 'main' into fix/consider_renamed_from

* normalize_inherits

* improve find_preset2 performace
2026-06-27 22:15:43 +08:00
Ioannis Giannakas
0e4928f200 Introduce minimal chamber temperature field (gcode chamber_min_temperature (#14340)
Introduce minimal chamber temperature field (gcode chamber_min_temperature)
2026-06-27 13:27:01 +01:00
SoftFever
8cb2e4e01e profiles: deterministic setting_id from vendor/type/name (#14432)
* profiles: enforce globally-unique, per-vendor-namespaced setting_id

Many non-Bambu vendors copied Bambu's generic setting_ids (GFSA04 alone
appeared in 1557 files), so setting_id was not globally unique. This
namespaces every vendor's ids and reserves Bambu/OrcaFilamentLibrary space.

- Reserve "G*" (Bambu) and "O*" (OrcaFilamentLibrary) id spaces.
- Assign each other vendor a 2-char prefix (first+last letter, collision
  resolved) and renumber every instantiated preset to <PREFIX><NNNN>.
- Strip setting_id from base profiles (instantiation:false) per Bambu's
  convention; assign one to instantiated presets that lacked it.
- Remove the pre-existing misspelled "settings_id" key (91 files).
- filament_id is left untouched (it is a per-material id).
- Add one-time migration script scripts/assign_vendor_setting_ids.py with a
  persisted registry resources/profiles/vendor_prefixes.json. Re-runs freeze
  existing ids; only new vendors/profiles get new ids.
- Bump version in each changed vendor index file.
- Extend scripts/orca_extra_profile_check.py with a CI guard: global
  uniqueness, in-namespace, no base setting_id, no gaps, no settings_id typo.

7425 profile files changed across 61 vendors; 0 cross-vendor collisions;
validator clean; migration idempotent. BBL and OrcaFilamentLibrary id spaces
untouched.

* profiles: add setting_id authoring guide for new vendors / profiles

* profiles: drop in-repo README; setting_id guide now lives in the wiki

* profiles: derive setting_id deterministically from vendor/type/name

* bump profile version
2026-06-27 20:11:25 +08:00
Felix14_v2
3b58217b47 Review changes in Russian localization (#14422) 2026-06-26 16:54:36 -03:00
ExPikaPaka
df95a30a0f Fix missing filament profiles in Filament Selection dialog (#14398)
* Resolve preset type based on nozle diameter if printer_variant is empty

* Fix incorectly resolving plastic type (PLA, ABS, etc.)

* Revert default print-variant handling as it can be not only nozzle diameter

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-26 18:37:33 +08:00
SoftFever
a1a9a0ce2b CI: add _x64 suffix to Windows installer/portable names for consistency with arm64 2026-06-26 16:39:13 +08:00
SoftFever
b7ab7f4225 update locale 2026-06-26 11:56:29 +08:00
Myself
465125167b CrealityPrint: remove hardcoded :4408 from Device WebView URL (#14326)
* CrealityPrint: use printhost_port for WebView URL instead of hardcoded :4408

Fixes #4408 — when printhost_port is configured, use that port in the
Device WebView URL instead of always defaulting to :4408.

* CrealityPrint: remove hardcoded :4408 from Device WebView URL

Drop the CrealityPrint-specific get_print_host_webui override that
unconditionally appended :4408. The generic fallback in PrintHost
already uses print_host_webui/print_host config — users who need
Mainsail on port 4408 should set print_host_webui explicitly.

Fixes #4408
2026-06-26 02:21:42 +08:00
SoftFever
19755451a9 CI: download profile validator from nightly release 2026-06-26 02:14:57 +08:00
pallaswept
5d352294ed Allow appimage execution for non-owner (#14339) 2026-06-26 01:27:47 +08:00
SoftFever
e3b4c6df84 Fix AMS overwrite sync showing filaments in wrong order (#14335) (#14408)
Syncing the filament list from AMS with the "Overwriting" option
  displayed the AMS filaments in the wrong (reversed) order in the
  preview, even though the filament mapping that was actually applied
  was correct. Reported on macOS with a Bambu X1C.

  The preview depends on MaterialHash being iterated in material-index
  order. MaterialHash is a WX_DECLARE_HASH_MAP, which under OrcaSlicer's
  current wxWidgets 3.3 build (wxUSE_STD_CONTAINERS=1) resolves to
  std::unordered_map; its iteration order is unspecified and on macOS
  (libc++) comes out reversed, which scrambled the preview. The same
  code is unaffected in BambuStudio because its older wxWidgets build
  still uses wx's own key-ordered hash table, so this only became
  visible after the wx upgrade.

  Fixes #14335
2026-06-26 01:20:38 +08:00
yw4z
b06305f990 Plate toolbar improvements and fixes (slice button, keep view angle on slice, review button for failed plates, percentage and improve failed state) (#14292)
* Update Plater.cpp

* init

* Revert "Update Plater.cpp"

This reverts commit ae515bc7a4.

* fix empty plate showing as failed

* keep zooming on slice

* update

* add hover effect for active plate

* fix condition and match code

* Update GLCanvas3D.cpp

* fix button colors

* add hover effect to border of plate stats button

* fix scrollbar disappear while clicking plate buttons

* Update GLCanvas3D.cpp

* fix slicing percent for new plates

* update

* Update GLCanvas3D.cpp

* update plate states on config change

* fix non functional scrollbar
2026-06-25 22:31:33 +08:00
SoftFever
dbd9c22d80 feat: native Windows ARM64 build support (Snapdragon X Elite) (supersedes #14059) (#14381)
* feat: native Windows ARM64 build support

Builds on the merged DEPS_ARCH=arm64 plumbing (#13424) by adding the
dependency and source fixes needed for a green native ARM64 build on the
windows-11-arm runner. Validated end-to-end on Snapdragon X Elite hardware
(via a downstream fork using the same fixes); see OrcaSlicer/OrcaSlicer#8271
for the full writeup.

Dependencies:
- OpenEXR 2.5.5: ImfSimd.h hard-codes IMF_HAVE_SSE2 for any MSVC, pulling in
  <emmintrin.h> (x86-only) -> C1189. Patch the header to require an x86 target
  and force SSE cache vars off on ARM64.
- Boost.Context: use the winfib implementation on ARM64 (Windows Fiber API)
  to avoid the armasm64 / CMake ASM_ARMASM linker-module bug, while keeping
  the Boost::context target Boost.Asio needs.
- OpenCV: disable WITH_IPP on ARM64 (Intel IPP/IPP-ICV is x86/x64 only;
  otherwise ~200 unresolved ippicv* externals at link).
- OpenSSL: use VC-WIN64-ARM on ARM64.
- FindGLEW: add an ARM64 arch branch.

Sources:
- clipper Int128.hpp: _mul128 is an x64-only intrinsic guarded by _WIN64
  (true on ARM64); guard on _M_X64 and use the portable path.
- imgui imgui_widgets.cpp: fix va_start(vaList, &text) -> va_start(vaList, text)
  (the &-form compiled on x64 but is invalid on ARM64).
- crash reporter: StackWalker.cpp gains an _M_ARM64 branch; BaseException.cpp
  uses Cpsr instead of the x86-only EFlags on ARM64.

CI:
- New build_windows_arm64.yml on windows-11-arm: pins CMake 3.31.x, stages
  ARM64 GMP/MPFR from MSYS2 clangarm64 (with llvm-dlltool import libs),
  caches deps with a fixed-depth hashFiles key, builds and uploads the binary.

OCCT/STEP, SVG-to-3D and text emboss all build and work on ARM64 (no stubs
needed). Full feature parity with x64.

* fix(ci): use forward-slash DESTDIR to avoid CMake '\a' escape error

deps configure failed at GMP/GMP.cmake: "Invalid character escape '\a'"
because DESTDIR carried Windows backslashes (C:\a\...) and is re-parsed
when re-set with the /usr/local suffix. Pass DESTDIR (and the slicer's
DEPS prefix) with forward slashes via %CD:\=/%.

* fix(ci): don't export DESTDIR env var (CMake staged-install doubles paths)

Setting a DESTDIR *environment* variable made CMake treat it as the staged
install prefix and prepend it to every dependency's install path, so e.g.
FreeType installed to <DESTDIR>/a/.../OrcaSlicer_dep/usr/local and OCCT
then couldn't find its headers. Compute the forward-slash path into a
differently-named var (ORCA_DESTDIR) and pass it only via -DDESTDIR.

* ci(windows-arm64): fold ARM64 build into the standard Windows matrix

Replace the standalone build_windows_arm64.yml with a matrix entry on the
existing build_windows job, so x64 and ARM64 share one reusable workflow
chain (build_all -> build_check_cache -> build_deps -> build_orca), per
review feedback on #14059.

- build_all.yml: build_windows now matrices over {x64: windows-latest,
  arm64: windows-11-arm} and threads `arch` through. Self-hosted runner
  stays x64-only.
- build_check_cache.yml: cache key and dep-prefix path are now
  architecture-specific on Windows (deps/build-arm64/OrcaSlicer_dep).
- build_release_vs.bat: accept an `arm64` argument (mirrors
  build_release_vs2022.bat) -> uses `-A ARM64` and the build-arm64 tree.
  The top-level CMake auto-derives CMAKE_PREFIX_PATH from the build dir,
  so no explicit prefix is needed.
- build_deps.yml / build_orca.yml: gate the ARM64-only prep behind
  `inputs.arch == 'arm64'` -- pin CMake 3.31.x, and stage MSYS2
  clangarm64 GMP/MPFR import libs. NSIS installer/PDB/profile_validator
  remain x64-only; ARM64 ships the portable zip. Artifact names get an
  arch suffix to avoid collisions between the two Windows jobs.

https://claude.ai/code/session_0164c7ZhCLsYBmCiVN9pWDjK

* ci(temp): generate GMP/MPFR win-arm64 blobs to commit to repo

* feat(deps): add prebuilt GMP/MPFR win-arm64 blobs

The repo ships prebuilt GMP/MPFR import libs + DLLs for win-x64 and
win-x86; the Windows ARM64 build path copies from win-${DEPS_ARCH}
(CMakeLists.txt) but the win-arm64 blobs were missing, so the slicer
configure failed at "file COPY cannot find .../win-arm64/libgmp-10.dll".

Add win-arm64 libgmp-10.{dll,lib} and libmpfr-4.{dll,lib}, generated from
the MSYS2 clangarm64 gmp/mpfr packages with MSVC-compatible import libs via
llvm-dlltool. Headers are shared across arches and unchanged.

* simplify OpenEXR.cmake

* set default arch

* support msix

* ship installer

* try to fix webview2runtime issue

---------

Co-authored-by: Adam Behrman <adam.behrman@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Adam Behrman <abehrman@users.noreply.github.com>
2026-06-25 22:10:49 +08:00
wujie
67645760f2 Add Elegoo Centauri 2 machine profiles (#14402)
* feat(profiles,elegoo): add Elegoo Centauri 2 machine profiles

* chore(profiles,elegoo): add type/from to Elegoo process presets
2026-06-25 22:08:27 +08:00
NM
6c7f3629bc fix(profiles): Snapmaker U1 — add bed dimensions to fdm_U1 for all nozzle variants (#14391)
fix(profiles): add printable_area to fdm_U1 so all nozzle variants inherit correct 270x270 bed

The 0.2/0.6/0.8 nozzle profiles inherit from fdm_U1 which had no
printable_area defined, causing them to fall back to a smaller default
bed size. The 0.4 profile was the only one that set it explicitly.

Move printable_area and printable_height to the shared parent (fdm_U1)
so all Snapmaker U1 nozzle variants get the correct 270x270mm bed.
Bump vendor version to trigger profile re-sync on existing installs.

Co-authored-by: ni4223 <ni4223@users.noreply.github.com>
2026-06-25 22:04:10 +08:00
SoftFever
688127af5b Specify plate index for the 3mf workflow (#14404)
feat: forward plateindex for index-coded .gcode.3mf uploads

Gcode inside a .gcode.3mf is index-coded (Metadata/plate_<N>.gcode) and a
bundle may carry several, so the upload must name which plate to print —
even a single-plate bundle, since its entry is still indexed.

A 1-based plate index is stored in PrintHostUpload::extended_info when use_3mf
is set; the OctoPrint and Moonraker hosts forward it as a `plateindex` form
field. Servers that don't use it ignore the unknown field, so the plain G-code
path is unchanged.
2026-06-25 22:02:19 +08:00
SoftFever
0c5d85e516 Fix/skirt flow crash empty plate (#14403)
* Fix crash when arranging with per-object skirt on empty plate
2026-06-25 18:50:58 +08:00
Noisyfox
cdc336d80c Merge remote-tracking branch 'upstream/main' into dev/extruder-toggle-wip 2026-06-25 12:46:35 +08:00
Noisyfox
eae002da13 Add unit test for per-extruder motion limit 2026-06-25 12:44:43 +08:00
Noisyfox
b7240ab1c6 Use max limit when current extruder is unknown 2026-06-25 12:20:13 +08:00
Noisyfox
1ab34a7454 Use per-extruder motion limit 2026-06-25 11:14:11 +08:00
SoftFever
da074043f2 Fix fuzzy skin artifacting regression (#14376) (#14382)
Fix fuzzy skin artifacting (#14376)
2026-06-25 10:52:19 +08:00
SoftFever
4e03983426 build belt parallel branch and publish the nightly release 2026-06-24 21:02:57 +08:00
dependabot[bot]
d3ac5ab98e Bump actions/github-script from 7 to 9 (#13431)
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 20:35:21 +08:00
SoftFever
0b77321d3a bump flashforge profile version 2026-06-24 19:16:15 +08:00
Noisyfox
7d0fd272b5 Merge remote-tracking branch 'upstream/main' into dev/extruder-toggle-wip
# Conflicts:
#	src/libslic3r/GCode.cpp
2026-06-24 18:22:32 +08:00
Markus K.
a6816d8c0d Fix hyperlink for flow ratio calibration wiki (#14378) 2026-06-24 12:23:55 +03:00
Kiss Lorand
045179150f Refactor skirt/brim + bugfixes related to them (#14333)
Refactor skirt and brim ownership and emission flow

Refactor skirt and brim generation around a common object/group
ownership model.

Skirts and brims are now emitted as a coordinated preamble
(skirt -> brim -> object) instead of being generated and emitted
through multiple independent code paths.

Changes:
- Fix repeated skirt emission caused by the previous skirt state
  tracking logic.
- Restore local skirt/brim ordering for per-object skirts in
  By Layer mode.
- Emit brims together with their owning object or object group.
- Handle combined brims independently from skirt grouping.
- Handle draft shields through the same ownership model as skirts.
- Fix draft shield generation when skirt height is zero.
- Generate draft shields after brim geometry is known, preventing
  draft shields from overlapping brims.
- Reject unsafe grouped per-object skirt configurations in
  By Object mode.
- Remove legacy skirt emission paths and state-management
  workarounds.

Support brim generation remains unchanged.

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-24 15:48:31 +08:00
SoftFever
4c3e30144d Show a warning dialog about profile sync changes. (#14377) 2026-06-24 14:58:33 +08:00
Indy Jones
ff83aa41ef Fix duplicate Flashforge filaments in system filament list (#14316)
The `@FF AD5M 0.25 nozzle` filament variants carried the base profile's
full printer list (AD5M/AD5M Pro/AD5X 0.4/0.6/0.8) instead of the 0.25
nozzle printers. Combined with base profiles that also listed the AD5X
0.4/0.6/0.8 printers already covered by dedicated `@FF AD5X` variants,
multiple presets with the same alias became compatible with the same
printer. The filament combobox keys presets by full name but displays
them by alias, so these surfaced as duplicate entries (e.g. "Flashforge
PLA Silk", "Flashforge ASA Basic" shown twice).

Fix the `compatible_printers` lists (data only, no settings changed):
- Repoint the 15 `@FF AD5M 0.25 nozzle` variants to the actual 0.25
  nozzle printers (Adventurer 5M 0.25 + Adventurer 5M Pro 0.25).
- Remove the redundant AD5X 0.4/0.6/0.8 entries from the base profiles
  where dedicated AD5X variants already exist.
- Bump Flashforge profile version to 02.04.00.02.

Each affected filament now resolves to exactly one preset per printer,
and the previously uncovered AD5M 0.25 nozzle printers gain coverage.
2026-06-24 14:20:11 +08:00
NM
91b712a237 feat(profiles): Snapmaker U1, add 0.2mm & 0.8mm nozzle profiles, complete 0.6mm process lineup (#14305)
* Snapmaker U1: add 0.2mm and 0.8mm nozzle profiles

Add machine and process profiles for the Snapmaker U1's 0.2mm and
0.8mm nozzles, and complete the 0.6mm process lineup. Follows the
same data-only pattern used to add the 0.6 / 0.4+0.6 nozzles in
commit afc3756.

The U1 ships with 0.4, 0.4+0.6 and 0.6 nozzle options today; the 0.2
and 0.8 nozzles are supported hardware but have no profiles, so they
cannot be selected. This adds them the Orca-native way: per-nozzle
machine presets plus a model-file dropdown entry, with their process
profiles filtered in via compatible_printers.

Machine (2): lean presets inheriting fdm_U1, mirroring the existing
SM_U1_06 (0.6) preset and overriding only the per-nozzle values;
setting_ids SM_U1_02 / SM_U1_08.

Process (21): 2 per-nozzle commons (fdm_process_U1_0.2_common,
_0.8_common) holding the nozzle line widths, plus 19 profiles
(0.2: 8, 0.6: 6, 0.8: 5) that inherit their per-nozzle common and
carry their own layer height, matching upstream's factoring. The two
0.24 Standard profiles that shared id GP029 are split into
GP029_06_024 / GP029_08_024.

Model dropdown: machine/Snapmaker U1.json nozzle_diameter
"0.4;0.4+0.6;0.6" -> "0.2;0.4;0.4+0.6;0.6;0.8".
Vendor index: register the new presets in Snapmaker.json.

The existing 0.4 / 0.6 / 0.4+0.6 presets resolve identically before
and after. scripts/orca_extra_profile_check.py and the profile
validator both pass.

* chore(profiles): bump Snapmaker vendor version to 02.04.00.04

Bump the Snapmaker vendor config_version so existing installs pick up the new 0.2mm and 0.8mm U1 nozzle profiles. PresetUpdater only re-imports a vendor bundle when the shipped version is strictly greater than the cached one.

---------

Co-authored-by: ni4223 <ni4223@users.noreply.github.com>
2026-06-24 14:18:38 +08:00
TheLegendTubaGuy
8491f87ddb Set Qidi X-Max 4 printer agent (#14334)
* Set Qidi X-Max 4 printer agent

* bump version

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-24 14:16:20 +08:00
Noisyfox
d13958ac54 Emit max value of machine limit among used extruders 2026-06-24 14:16:13 +08:00
Sandmann
99fde4a24e fix(CrealityPrint): avoid false 'End of file' error when K1 closes the WS after start (#14344)
K1C: corrige erro 'End of file' ao enviar impressao (start_print)

A K1-family fecha o WebSocket 9999 assim que aceita o comando de iniciar
impressao. O start_print fazia um ws.read() bloqueante logo apos o write, que
estourava 'End of file [asio.misc:2]' e era reportado como erro -- embora a
impressao ja tivesse iniciado (o comando e entregue no write). Torna o read e o
close best-effort (overloads com error_code), eliminando o falso erro. Mesmo
padrao ja usado em feed_filament; cobre os caminhos single-color e multicor.
2026-06-24 14:13:45 +08:00
Noisyfox
599f606f6f Make machine_max_junction_deviation multi-variant, just like all other motion ability parameters 2026-06-23 22:40:07 +08:00
Noisyfox
cc6929e506 Fix issue that modified highlight not properly handled for printer_options_with_variant_2 options 2026-06-23 22:21:49 +08:00
Noisyfox
14ad533d91 Fix applying of options in printer_options_with_variant_2 list 2026-06-23 22:01:16 +08:00
Ian Bassi
5538bf6463 Lang: Gettext update (#14361) 2026-06-22 20:16:55 -03:00
foXaCe
fdf4a493cb i18n(fr): translate strings added after the post-refactor sync (#14304) 2026-06-22 20:13:19 -03:00
Heiko Liebscher
932410e06b Improve German (de) translation (#14352)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:40:14 -03:00
Noisyfox
981cd599d5 It is in fact obj config 2026-06-22 20:51:40 +08:00
Noisyfox
9ec93deeda Fix more unit tests 2026-06-22 20:51:23 +08:00
Noisyfox
99dea01cc3 Fix coord out-of-range exception caused by m_origin memory not initialized to 0 2026-06-22 20:50:00 +08:00
SoftFever
8522cc5f67 fix 1. default_acceleration was set to 20000 in calib_cornering 2. pa_pattern should use obj_config 2026-06-22 18:14:39 +08:00
SoftFever
b226919217 Fix an issue that speed_values is double inserted 2026-06-22 18:13:22 +08:00
Noisyfox
9ee357b77f Fix unit tests 2026-06-22 17:59:27 +08:00
SoftFever
ca388f2d9e Merge branch 'main' into pr/Noisyfox/13712 2026-06-22 10:56:11 +08:00
SoftFever
a409791826 bump version to 2.5.0-dev 2026-06-22 00:50:51 +08:00
Noisyfox
ba12608e4a Don't allow adding more colors for non-semm printers on obj import color remapping dialog (#14275) 2026-06-21 18:20:58 +08:00
Indy Jones
b8dd2d3ca8 Fix start G-code: wait for nozzle temperature before purge line (#14120)
Several Artillery and Flashforge machine profiles set the first-layer nozzle temperature with M104 (set, no wait) immediately before the purge/prime line. The purge then runs before the nozzle reaches temperature, so filament is extruded through a nozzle that is not yet hot enough to melt it. Changed M104 to M109 so the printer waits for the target temperature before purging.

Affected profiles:
- Artillery Sidewinder X3 Plus / X3 Pro / X4 Plus / X4 Pro (0.4 nozzle)
- Flashforge AD5X (0.25/0.4/0.6/0.8)
- Flashforge Adventurer 5M / 5M Pro (0.25/0.8 overrides + shared fdm_adventurer5m_common, which also covers the 0.4/0.6 variants via inheritance)

Refs #4337
2026-06-20 20:16:37 +08:00
Alexandre Folle de Menezes
0335d76d30 Update and complement ptBR translation (#14302) 2026-06-20 13:15:28 +08:00
Mykola Nahirnyi
3e56d25f64 Allow presets without parent 2026-06-20 13:14:46 +08:00
Tobias Gloth
4c149b69eb can build boost, draco, opencv as debug with msvc (#13921)
* can build boost, draco, opencv as debug with msvc

* only forward build configuration for MSVC and in Debug mode
2026-06-20 12:39:08 +08:00
Terasit Juntarasombut
57cb60e20f l10n: Update Thai (th) localization after gettext refactor (#14288) 2026-06-19 20:40:09 -03:00
Surfoo
8ffe84b1dd i18n(fr): improve French localization quality and consistency. (#14293) 2026-06-19 17:09:39 -03:00
Ian Bassi
f4268a0eec Adaptive Pressure Advance Validation (#14198)
Co-authored-by: Wegerich <23041237+Wegerich@users.noreply.github.com>
2026-06-19 15:46:00 -03:00
SoftFever
17e2adc283 CI(macOS): retry flaky hdiutil DMG creation; delete per-arch bundles only on success 2026-06-20 01:15:31 +08:00
Ian Bassi
d87f7e462c Localization fixes (#14291)
* Restore text

* Restore english fuzzys

* Gettext

* Fuzzy for comment

* Gettext
2026-06-19 11:54:53 -03:00
SoftFever
0a6a42ecb5 Add Linux ARM64 (aarch64) AppImage build
Build the Linux AppImage for ARM64 (aarch64) alongside x86_64: the Linux CI
job now matrixes over both architectures, with arch-aware deps caching and
artifact/asset names (amd64 keeps its existing names). The aarch64 AppImage is
published to the nightly and release pages like the x86_64 one.

Run the unit-test suite on the aarch64 runner (faster GitHub arm runner); the
tests are built on that leg. Self-hosted keeps tests on the amd64 server.
2026-06-19 15:57:20 +08:00
Kiss Lorand
762e474433 Skirt overhaul (#14130)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-06-19 10:43:46 +08:00
Rodrigo Faselli
115d6dde46 ENABLE_SMOOTH_NORMALS (#14080)
* ENABLE_SMOOTH_NORMALS

* Remove definition of macro L if defined

* Update GLModel.cpp

* suavizado ajustado en 5 grados

5 grados

3,5 grados

* Ajuste de brillo menos intenso

* opcion smooth normals

Update GLModel.cpp

test

test 3

* cleaning macros

* tooltip

* Apply suggestion from @RF47

* Apply suggestion from @RF47

* Apply suggestions from code review

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-06-19 09:42:12 +08:00
foXaCe
9da2975424 i18n(fr): complete French catalog after the localization refactor (#14277) 2026-06-18 16:19:24 -03:00
raistlin7447
7b3228d10d Cover the libnest2d nesting engine and fix an NfpPlacer crash (#14267)
* fix(libnest2d): skip the excluded-region alignment pass when there are none

NfpPlacer::finalAlign(), run from clearItems() and the destructor, always
ran the "find a best position inside the NFP of fixed items" pass even when
no items are fixed. With nothing to avoid, calcnfp() computes the inner-fit
NFP of the pile and can feed clipper a coordinate outside its allowed range.
On Linux/clang the value stays in range so it went unnoticed; on MSVC the
clipper "Coordinate outside allowed range" exception escapes the noexcept
destructor and aborts the process (exit 0xC0000409).

Build the excluded set up front and only run the pass when it is non-empty.
The block exists solely to keep the pile clear of fixed items (excluded
regions / wipe tower), so it is a no-op when there are none and the
wipe-tower behaviour is unchanged.

* test(libnest2d): remove dead nesting tests and split the suite by feature

Seven of the suite's hidden [.] test cases drove code paths Orca abandoned
at the BambuStudio fork: BottomLeftPlacer (used nowhere in src/) and the
stock default NfpPlacer backend, which returns zero bins in Orca. They have
been red since the fork and are never registered with ctest. Remove them.

Split the 1,000-line libnest2d_tests_main.cpp into per-feature files, per the
repo convention, sharing a header for the no-fit-polygon backend setup that
every translation unit must agree on (ODR):

  libnest2d_tests.cpp       Item and nest() basics
  test_geometry.cpp         geometry primitives
  test_nfp.cpp              no-fit-polygon machinery
  libnest2d_test_utils.hpp  shared includes and the NFP backend specialisation

Along the way: drop a debug exportSVG() helper that only wrote a file on test
failure (so the suite never leaves stray assets), convert the deprecated
Catch::Approx to WithinRel/WithinAbs matchers, and give the tests descriptive
names.

* test(libnest2d): add NfpPlacer unit tests

NfpPlacer is the placement engine the arranger drives, but the suite only
covered the geometry primitives. Add a fixture and five tests that exercise
pack()/accept() directly: a single item lands in the bin, an oversized item
is rejected, the first item is seeded for every starting point, many items
pack without overlap, and the rotation candidates are searched. This lifts
nfpplacer.hpp line coverage from 42% to 87% in the libnest2d suite.

* test(libslic3r): add arrangement::arrange() integration coverage

The libnest2d suite cannot reach Orca's real nesting entry point because it
does not link libslic3r. Add test_arrange.cpp driving arrangement::arrange():
items land on the bed and within bounds, do not overlap, are spaced by their
inflation, an oversized item stays unplaced, overflow spills onto virtual beds,
an empty input is a no-op, and the DONT_ALIGN and USER_DEFINED final-alignment
paths are exercised. A self-test guards the overlap check the other cases use.
2026-06-18 23:40:37 +08:00
Ian Bassi
2de58e557b Lozalization Update after refactor (#14272) 2026-06-18 09:22:31 -03:00
Ian Bassi
514ab02525 Localizations refactor (#14254) 2026-06-18 09:13:51 -03:00
Wegerich
d7688a27d0 APA for overhangs - Prusa incompatibility warning (#14271)
* APA for overhangs - Prusa incompatibility warning

Added a sentence explicitly stating that APA for overhangs is not compatible with prusa printers

* whitespace
2026-06-18 11:46:51 +01:00
Wegerich
c2965a1336 Clarify that "network plugin" now means *bambu* only (#14265)
Clarify that "network plugin" now means *bambu* only and doesn't refer to orca cloud

fully differentiate the two offerings to avoid confusion especially for non Bambu users
2026-06-18 15:10:56 +08:00
raistlin7447
a587859e84 Fix: show all print validation warnings instead of only the last (#14112) 2026-06-18 12:45:26 +08:00
SoftFever
3ffb9585d2 flatpak: OrcaSlicer was trying to migrate old configs from an io.github.orcaslicer.OrcaSlicer folder — a bundle ID used only in nightly builds between the 2.3.1 and 2.3.2 releases.
The correct old config folder, widely used in pre-2.3.2 releases, is io.github.softfever.OrcaSlicer.
2026-06-18 00:40:22 +08:00
Alexandre Folle de Menezes
af854f3242 Improve and complement the pt-BR translation (#14252) 2026-06-17 23:57:11 +08:00
Noisyfox
9b60b9cd5d Fix wrong variant index is used on motion ability setting tab (#14253)
Fix wrong variant index is used on motion ability setting tab (OrcaSlicer/OrcaSlicer#13308)
2026-06-17 23:54:27 +08:00
SoftFever
927c5efb8c add Chinese translations 2026-06-17 20:53:01 +08:00
SoftFever
81951fddb5 Fix locale errors 2026-06-17 19:10:29 +08:00
Alexandre Folle de Menezes
4535f19501 Misc fixes to GUI strings (#14047)
* Degrees symbol don't need localization

* The Z when referring to the axis should be uppercase

* Fix the spelling of "GitHub" to camelcase

* Unify the casing of mouse button shortcuts

* Always use G-code with an hyphen

* Fix the spelling of "restricted"

* More grammar fixes

* add missing modifications

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-17 17:55:32 +08:00
Jakub Hencl
ee9a796f14 Improve Czech localization translations (#13666)
* Improve Czech localization translations

Updated and improved Czech translations for OrcaSlicer UI strings.

- Fixed untranslated entries
- Improved terminology consistency
- Adjusted wording to better match PrusaSlicer style
- Fixed plural forms and formatting placeholders
- Remaining untranslated strings will be completed later

* fix errors

* reformat

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-17 17:45:16 +08:00
Heiko Liebscher
3bce4bb197 feat: update German localization for various features and error messages (#14201) 2026-06-17 17:18:08 +08:00
Noisyfox
dcee299909 Allow use offline when logged in to Orca Cloud (#14235)
* Store user session information along with refresh token, to allow offline use once user is logged in

* Don't bother with avatar because we won't see it when offline anyway

* Fix offline Sync Presets freezing the UI on repeat clicks

Ignore restart_sync_user_preset() while a manual sync's progress dialog is on screen, so a second app-modal dialog can't stack on the first. Offline the dialog blocks on a long, uncancellable HTTP timeout; on macOS the global menu stays live while the window is disabled, so a second click otherwise wedges the app (force-quit only).

* Skip redundant user-secret re-write on startup

set_user_session() always re-encrypts and writes the secret to disk; on the startup restore path that just rewrites the bytes it was loaded from. Add a persist flag so the restore path skips it. Also drop an unused catch binding and a stray blank line.

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-17 17:15:09 +08:00
SoftFever
9eeb73b68b feat: Support 3MF as g-code (use_3mf option) (#14238)
* feat: add support for 3MF file format in printer configurations and export options

* fix file extension

* enable 3mf for X Max 4

* disable use_3mf for X Plus 4

* Fixed an issue where `label_object_enabled` was not properly propagated to 3mf

* enable exclude object for Max 4

* remove hardcoded use 3mf for flashforge, move them to the new printer profiles config
2026-06-17 15:39:40 +08:00
hamham999
7ab3174f7c Fixing the volumetric speed setting for some Creality filaments (#14237) 2026-06-16 18:16:01 -03:00
Vovodroid
5a6e31ac5b Allow drop of sunken objects (#14243) 2026-06-16 18:13:10 -03:00
Kiss Lorand
e700113b39 Fix non-organic tree support base clearance above object surfaces (#14128) 2026-06-16 15:50:09 -03:00
Grant Harkness
5ed8f5ef25 perf(GCodeProcessor): stop recompiling std::regex on every g-code line (up to 2.9x faster slicing) (#14166)
perf(GCodeProcessor): stop recompiling std::regex on every g-code line

process_SET_VELOCITY_LIMIT() constructed three std::regex objects from
scratch on every call, and Klipper-flavor g-code contains
SET_VELOCITY_LIMIT on a large share of lines (8,834 of 103,549 lines for
a single 3DBenchy sliced for a Creality K2). perf attributes 6.4% of the
whole slicing run to this one function, almost all of it regex
compilation and the allocator traffic it generates.
process_SET_PRESSURE_ADVANCE() and the External_Purge_Tag handler had
the same per-call construction.

Hoist all five patterns to function-local static const std::regex so
they compile once. Generated g-code is byte-identical (modulo the
timestamp header); slicing a 16x Benchy plate for a K2 drops from
78.5s to 27.3s wall (2.9x) on a 16-core Linux box, single Benchy from
8.9s to 5.6s.

Co-authored-by: grant0013 <grant@harktech.co.uk>
2026-06-16 23:51:27 +08:00
raistlin7447
454335dba6 Correct tests/CLAUDE.md: testing framework is Catch2 v3, not v2 (#14231)
The testing guide stated OrcaSlicer uses Catch2 v2 and advised the v2
`<catch2/catch.hpp>` include, but the vendored framework is v3.11.0
(tests/catch2/) and every test file includes `<catch2/catch_all.hpp>`.

The wrong version drove several incorrect claims: that SKIP() is
unavailable (it is, v3.3.0+), that the string matcher is "Contains"
rather than "ContainsSubstring", and that thread-safe assertions,
multiple reporters, STATIC_CHECK and built-in sharding do not exist.

Correct all version statements, the example include, and the
former "Version-Specific Limitations" section to reflect v3.11.0.
2026-06-16 23:00:30 +08:00
Allyn Malventano
b0c1887f40 fix: persist user-selected preview view mode after first load (#13625)
fix: apply smart preview defaults per extruder count session

- Track last extruder count (1=single, 2+=multi) instead of boolean flag
- Apply appropriate default (ColorPrint/FeatureType) when count changes
- User selections persist within same extruder count
- Symmetric behavior: both single and multi actively apply defaults
- Delete duplicate dead code block (uncommented TODO scaffolding)

Behavior:
- First slice (any type) → appropriate default
- User changes view → persists on re-slice
- Switch single→single or multi→multi → persists
- Switch single↔multi → appropriate default applies
2026-06-16 22:15:00 +08:00
raistlin7447
e6f917d7c5 Re-enable [OrcaCloudServiceAgent] headless tests now that the crash is fixed (#14236)
Re-enable [OrcaCloudServiceAgent] tests now that the headless crash is fixed

The two OrcaCloudServiceAgent display-name tests were tagged [NotWorking]
in #14175 because the agent constructor dereferenced a null wxTheApp when
run headless (no wxApp is created in the unit-test binary), crashing before
any assertion ran. That null dereference was fixed in 14d2dfdd4c, which
guards wxTheApp in compute_fallback_path() and skips file persistence when
no fallback path is available.

With the fix in place both tests build and pass headless, so drop the
[NotWorking] tag and the stale explanatory comments. Verified on Linux
clang-18 (the CI compiler), headless: 20 assertions in 2 test cases pass.

Closes #14193
2026-06-16 22:14:13 +08:00
SoftFever
14d2dfdd4c Fix test errors by guarding against null dereference of wxTheApp in OrcaCloudServiceAgent::compute_fallback_path() and skipping file persistence when no fallback path is available. 2026-06-15 14:29:30 +08:00
raistlin7447
b1e510fc99 Disable fff_print tests that fail only in CI (#14207)
* Disable fff_print tests that fail only in CI

Skirt height is honored, Scenario: Skirt and brim generation, and
Scenario: PrintGCode basic functionality slice geometry that makes clipper's
coordinate range check throw "Coordinate outside allowed range" in the Linux
CI environment, while the same tests pass in local builds. Tag them
[NotWorking] so the Unit Tests job (ctest -LE NotWorking) excludes them until
the underlying slicing issue is fixed in a follow-up PR.

* Trigger Build all workflow on tests/** changes

The push and pull_request path filters did not include tests/**, so a
test-only change never started the build and the Unit Tests job never ran.
Add tests/** to both filters so changes to the test suite are built and
exercised by CI.
2026-06-15 08:53:45 +08:00
yw4z
8c0b89bc8a Launch troubleshoot center with clicking to dev button (#14178)
* init

* show tooltip for dev mode
2026-06-14 18:55:40 +08:00
Mikhail f. Shiryaev
3e2373c86e Improve build cache (#14014)
* Improve build cache: use it in deps, add sccache

* Add a way to to set `ccache` or `sccache` using CMAKE_CCACHE
2026-06-14 18:35:56 +08:00
Ian Chua
2abfa512ef feat: notification where there is an update available for subscribed presets (#14121)
* feat: notification where there is an update available for subscribed presets

* add tracking set for updates available
2026-06-14 18:30:42 +08:00
raistlin7447
fe0eafc02b Fix Unit Tests CI job that silently ran zero tests (#14175)
Fix Unit Tests CI job silently running zero tests

scripts/run_unit_tests.sh selected tests with `ctest -L "Http|PlaceholderParser"`,
but catch_discover_tests() was called without ADD_TAGS_AS_LABELS, so Catch2 tags
were never registered as CTest labels. The -L filter matched nothing and the job
passed green while running no tests ("No tests were found!!!"). Tests have not run
in CI since PR #11485 added that -L line (2025-12-23).

Register tags as labels via a shared orcaslicer_discover_tests() wrapper in
tests/CMakeLists.txt (passing ADD_TAGS_AS_LABELS), routed through all five test
suites. Restore full-suite execution by replacing the narrow -L selection with a
`-LE NotWorking` exclusion, so all reliable tests gate PRs again (the suite ran in
full before #11485).

Tag the two OrcaCloudServiceAgent display-name tests [NotWorking]: their
constructor reaches wxStandardPaths::Get().GetUserDataDir(), which dereferences
the null wxTheApp in the headless test binary and segfaults on every platform.
Excluded until the agent can be constructed without the wx app context.

CI now runs 151 tests (was 0) and passes.
2026-06-14 17:44:20 +08:00
raistlin7447
5fafbb59fc Revive the disabled fff_print test suite (#14196)
* Fix null-deref and arranger bugs that gate headless slicing tests

export_gcode dereferenced a null result out-param, enum serialization
dereferenced a null keys_map, and get_arrange_polys left bed_idx unseeded so
the arranger dropped items. All only affect the headless test/CLI path.

* Fix the headless test harness and add G-code test helpers

Use the real arranger, fix temp-file handling with an RAII guard, and add
layers_with_role / max_z for inspecting sliced G-code.

* Re-enable the Model construction test

* Re-enable SupportMaterial tests and add an enforced-support test

* Re-enable and extend PrintObject layer-height and perimeter tests

* Re-enable Print skirt, brim, and solid-surface tests

* Re-enable and extend PrintGCode tests

Un-hide the basic scenario (dead-key fixes, reframes, trimmed trivia) and add
initial-layer-height, sequential-order, and null-result export tests.

* Re-enable and reframe the skirt/brim tests

Detect skirt/brim by G-code role comment instead of a sentinel speed, and
resolve the previously-unfinished skirt-enclosure test.

* Replace the stale lift()/unlift() test with a z_hop test

* Delete the stub and broken Flow tests
2026-06-14 17:42:53 +08:00
Myself
abb4eddb9c Fix CrealityPrint uploads with non-ASCII filenames (#13833)
Fix Creality upload URLs for encoded filenames
2026-06-14 16:23:52 +08:00
yw4z
9bcee518f8 Filament list improvements (Filament counter & Auto unfold & Scroll to end of list on changes) (#14158)
* Update Plater.cpp

* filament counter
2026-06-14 12:04:34 +08:00
Michel (Mike) a.k.a VE2EXB
0c4c04bcaf Fix Arch Linux dependency installation (gstreamermm, webkit2gtk, -Syy) (#14171)
Fix Arch Linux dependency installation

The arch dependency script listed packages that are no longer available
in current Arch/CachyOS repositories:

- gstreamermm: removed from official repos (AUR only) and not referenced
  anywhere in the OrcaSlicer build; the build uses plain gstreamer.
- webkit2gtk: replaced upstream by webkit2gtk-4.1.

Also switch the install command from `pacman -Syy` to `pacman -Syu` to
avoid the partial-upgrade pattern that Arch officially discourages on a
rolling-release distro.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 01:24:43 +08:00
yw4z
5f8b48473a Show printer name on titlebar of printer section while its folded (#14148) 2026-06-13 20:45:42 +08:00
Ian Bassi
6bb7903b97 Spanish update + Desc update (#14165) 2026-06-11 22:07:05 -03:00
foXaCe
d641ca598a i18n(fr): fix untranslated UI strings, extract hardcoded ones, complete French catalog (#14164) 2026-06-11 17:10:31 -03:00
SoftFever
15f330641c Add Microsoft Store MSIX package build (#14142)
* docs: add MSIX Store build design spec

* docs: update MSIX spec (PFN deep link, .drc, Associate tab) and add implementation plan

* ci: add MSIX logo asset generator and generated assets

* ci: fix MSIX asset rendering edge bleed (PixelOffsetMode) and make output order deterministic

* ci: add MSIX AppxManifest template

* ci: add MSIX packaging script

* ci: make build_msix.ps1 stage-only exit dot-source safe

* ci: build MSIX Store package in Windows job

* ci: run MSIX pack after existing Windows uploads and keep it out of release downloads

* feat: add MSIX packaged-context detection helpers

* fix: resolve MSIX package APIs dynamically to keep Win7 loadable

* feat: suppress self-update in MSIX Store build

* feat: suppress runtime file associations in MSIX Store build

* feat: keep version check in MSIX build, point update dialog at the Store

The update check is notification-only (OrcaSlicer never auto-downloads),
so the Store build keeps checking for new versions instead of skipping
the check. What changes when packaged is the new-version dialog: the
Download button is hidden, the info text asks the user to update from
the Microsoft Store, and the hyperlink / wxID_YES action opens the Store
product page instead of the GitHub release page.

* docs: align spec verification plan with Store-redirect updater behavior

* feat: default MSIX identity to the reserved Partner Center values

* feat: render MSIX logos full-bleed from the gradient-circle SVG

* feat: point update dialog Download button at the Store in MSIX builds

* feat: link Associate tab to Windows Default Apps settings in MSIX builds

* docs: align spec with review-driven logo, dialog and Associate-tab changes

* clearn up
2026-06-11 23:56:16 +08:00
hamham999
d07cb462a8 Creality K series printer updated start gcode (#14118)
* edit startgcode for K series printers

* Update Creality K1 (0.4 nozzle).json
2026-06-11 14:06:25 +08:00
raistlin7447
93a82af6b4 Fix top row tile order in YOLO perfectionist flow calibration (#14132)
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-10 18:24:46 -03:00
Noisyfox
06eefe7c1e Make sure mouse position is updated before hit test (#14139) 2026-06-11 02:47:33 +08:00
Ian Chua
847807adac fix: tombstone resolution for 409 status code with error code -3 (#14116)
* fix: tombstone resolution for 409 status code with error code -3

* fix: add resolution for undefined conflicts

* fix: generate setting id if it is empty for 409 tombstone

* fix: force push empty setting_id preset on 409 tombstone

* clearner solution
2026-06-10 22:23:12 +08:00
wujie
3be36bdea4 Fix Elegoo CC2 layer stats G-code and missing Giga preset renamed_from (#14136)
* Sync Elegoo profiles from ElegooSlicer

Update vendor Elegoo.json, filament/machine/process trees, and OrcaFilamentLibrary
Elegoo entries. Align machine default material names with existing filament preset names.

* feat: expose filament_name for G-code export filename format

Derive from filament_settings_id for the first active extruder and strip the suffix after @, matching ElegooSlicer so filename_format can use {filament_name}.

* chore: reorder Elegoo entries in OrcaFilamentLibrary

Group Elegoo @base profiles and bump library version to 02.03.02.62.

* sync OrcaFilamentLibrary.json with Elegoo filament profiles

* fix: clean up Elegoo process renamed_from for profile validation

Add single renamed_from only where preset names changed from legacy Orca
names; remove duplicate Rapid @System library entries that conflicted with
ECC2 vendor presets.

* fix(profiles): add missing Elegoo renamed_from for profile validation

CI custom-preset tests still inherit legacy Orca preset names that no
longer exist after the Elegoo bundle update. Add renamed_from on process,
Neptune 4 machines, OrcaFilamentLibrary filaments, and Giga profiles so
inherits resolve again, without changing print parameters.

* fix(profiles,elegoo): resolve renamed presets and CC2 SET_PRINT_STATS_INFO G-code

Resolve legacy preset names through renamed_from when validating presets and loading external projects. Add missing renamed_from aliases for Elegoo Giga process and OrcaFilamentLibrary filaments. Combine TOTAL_LAYER and CURRENT_LAYER in one SET_PRINT_STATS_INFO command on Centauri Carbon 2 (ECC2), Centauri (EC), and Centauri Carbon (ECC) 0.4 nozzle profiles.

* chore(profiles): bump Elegoo and OrcaFilamentLibrary profile versions

Refresh installed profile bundles after renamed_from aliases, CC2 SET_PRINT_STATS_INFO G-code, and Preset.cpp renamed preset resolution fixes.
2026-06-10 21:38:39 +08:00
Heiko Liebscher
a70b903a19 Update German translations in OrcaSlicer_de.po (#14135) 2026-06-10 14:54:11 +08:00
Rodrigo Faselli
6d9ab28e2d Revert "skirt before brims (combine brim bugfix)" (#14131) 2026-06-09 20:25:34 -03:00
Rodrigo Faselli
38ea91a6bb skirt before brims (combine brim bugfix) (#14126) 2026-06-09 17:14:30 -03:00
Ian Bassi
c1f3125f75 Qidi q2 start gcode Update (#14111)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-06-08 16:47:47 -03:00
SoftFever
445bcdfa00 Fix/orca auth network glitch logout (#14110)
* fix(auth): don't log out of Orca cloud on transient token-refresh failures
2026-06-09 02:34:25 +08:00
yw4z
ff51abc69b Improve incremental compile speed on windows (#12881)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-06-08 11:30:10 -03:00
SoftFever
f3a5c169c9 Fix user preset load crash from inconsistent per-variant vector sizes (#14106)
* Fix user preset load crash from inconsistent per-variant vector sizes
2026-06-08 22:13:00 +08:00
Kiss Lorand
bd41eebba9 Fix junction deviation and jerk settings behavior (#14004)
* Fix junction deviation and jerk settings behavior

Process settings now follow the selected printer's junction deviation
configuration. When machine_max_junction_deviation is enabled,
default_junction_deviation is shown and jerk settings are hidden. When
junction deviation is disabled, jerk settings are restored and
default_junction_deviation is hidden.

Fix a validation issue where junction deviation mismatch warnings could
be reported even when machine_max_junction_deviation was set to 0.
Warnings now apply only when junction deviation is active and point
directly to default_junction_deviation.

Also simplify Motion ability page visibility checks by reusing local
firmware-flavor booleans.

* GUI tweak

- separate Junction Deviation segment
- JD and Jerk stay visible
2026-06-08 20:27:37 +08:00
SoftFever
1e1d9cbaf8 Fix crash on printer switch from stale filament/extruder indices (#14103)
Switching to a printer with fewer filaments (e.g. H2D -> X2D) threw
std::out_of_range in check_filament_printable. Clear stale per-volume
extruder config on count shrink and bound-check filament indices at the
read sites.
2026-06-08 18:57:52 +08:00
SoftFever
0d586b28c4 Fix handy model menu failing in RelWithDebInfo (#14102)
* Fix handy model menu failing in RelWithDebInfo; replace pointer-compared labels with a data table
2026-06-08 17:16:54 +08:00
SoftFever
8cc56a9e2a bump change vendor profiles version 2026-06-08 16:47:27 +08:00
Kiss Lorand
8a64a5dd55 Fix organic tree support bottom interface layer number fallback (#14095)
- fix organic tree support bottom interface layer number fallback when "Same as top" is used
2026-06-08 15:48:49 +08:00
Kiss Lorand
a1897873ab Follow-up for PR #14069 (#14092)
- release the smoothing absolute hard limit of PR #14069
-  fix organic tree root mesh generation for buildplate roots
2026-06-08 15:46:19 +08:00
Diego Casasnovas
b676fd32b2 Bambu Lab X2D 0.4 enable air filtration (#14090)
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-08 15:44:10 +08:00
Sam
d1c9eb4826 Fix SeeMeCNC Multicolor change (#14096)
* Fix SeeMeCNC Multicolor change

* Fixes for Support

Support fixes for .7 and 1.0 nozzles

* Fix Retractions

* FIX

* bump version

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-08 14:57:39 +08:00
Rodrigo Faselli
2f2ebfaf6e Fixing collisions during material changes on the Snapmaker U1 (#14097)
* Update Snapmaker U1 (0.4 nozzle).json

* geometric clearances fix and bump version

Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com>

---------

Co-authored-by: Ian Bassi <12130714+ianalexis@users.noreply.github.com>
2026-06-08 14:51:17 +08:00
SoftFever
765b3eaf1c Fix an issue wipe_tower_filament is still set to 1 in some profiles 2026-06-08 14:23:28 +08:00
SoftFever
8f65486e69 ci: fix PDB was not uploaded to release page 2026-06-08 12:24:51 +08:00
SoftFever
d8877e721b ci: add maintainer-gated workflow to publish build artifacts to a draft release (#14099) 2026-06-08 12:15:39 +08:00
SoftFever
41ff0f976c Fixed an issue that "Z contouring" is not translatable 2026-06-08 00:36:53 +08:00
SoftFever
02abe78074 Complete Zh_CN translations and a minor locale fix 2026-06-08 00:06:45 +08:00
SoftFever
968d85965b update locales 2026-06-07 23:13:48 +08:00
hamham999
1725a130fa Fixing process names and deleting old/duplicate processes of Creality Printers (#14085)
* Fixing process names and delete old/duplicate processes

* Update Creality CR-10 V3 0.6 nozzle.json

* naming fixes passes extra Profil valitadion
2026-06-07 22:57:57 +08:00
SoftFever
995facc3b7 bump profile versions for those have changes 2026-06-07 21:25:10 +08:00
SoftFever
10a1b17381 Feature/fix profile variants issus (#14084)
* add print variant validation

* fix one profile error

* fix more prifiles
2026-06-07 17:46:53 +08:00
spr0u
ff4147ede3 fix: stuck hover state after opening printer preset dialog (#14082)
* fix: stuck hover state after opening printer preset dialog
2026-06-07 15:24:17 +08:00
Argo
2a08bf026f Bugfix: VFA calibration disables input shaping (Marlin 2) (#14079)
VFA calibration does not disable input shaping anymore
2026-06-07 14:12:44 +08:00
Sam
9539dee052 Fixed speeds For SeeMeCNC 3D Printers (#14075)
* Fix speeds

* Fix

* Fix

* Wrong Syntax

* Fix Syntax

* Fix Jerk vals

* Speeds to accel fix

* Fix per nozzle size accel fix

* Create Z-Bolt.json

* seemecnc speeds fix
2026-06-07 13:33:18 +08:00
Kiss Lorand
454b6c0045 Fix crashing if slicing with non organic tree support (#14074)
Fix non organic tree support crashing

Fix Orca crashing during slicing if non organic tree support is used
2026-06-07 13:31:52 +08:00
spr0u
236a8f3941 fix: misc issues in step import dialog (#14067)
fix mesh count cancellation flag using uninitialized memory
fix initial waiting text being truncated
2026-06-07 13:30:13 +08:00
Kiss Lorand
4d86f5c250 Fix organic tree smoothing drift and layer mapping (#14069) 2026-06-07 13:28:58 +08:00
Rotzbua
d92bf80269 chore: win installer: remove duplicate code (#13998)
# Description

Remove duplicate code for creating desktop shortcuts.
The shortcut is already created by `CPACK_CREATE_DESKTOP_LINKS`.

# Reference


https://cmake.org/cmake/help/v4.3/module/CPack.html#variable:CPACK_CREATE_DESKTOP_LINKS

## Tests

- manual installation on win and check shortcut
2026-06-07 13:18:55 +08:00
SoftFever
fcfadf0e8c Creality K-series support: LAN discovery + CFS filament sync + filament profiles (#13752)
## Summary

Adds end-to-end Creality K-series (K2 / K2 Plus / K2 Pro) host support
to OrcaSlicer in a single bundle, per [@SoftFever's request to
consolidate](https://github.com/OrcaSlicer/OrcaSlicer/pull/13752#issuecomment-4560837450)
the previously stacked PRs. Three logically separable features, all
gated on `host_type=crealityprint`:

1. **LAN auto-discovery** — `Browse...` in the Physical Printer dialog
now finds K-series printers on the local network via a DNS-SD
meta-browser (per-device-unique service names
`_Creality-<MAC>._udp.local.`). Other host types unchanged.
2. **CFS filament sync** — `CrealityPrintAgent` (inheriting
`MoonrakerPrinterAgent`) queries the K-series WebSocket on `:9999` for
`boxsInfo`, maps loaded CFS slots to Orca filament presets, and
populates the Sidebar via the standard `fetch_filament_info` →
`build_ams_payload` path. Matches the shape of `MoonrakerPrinterAgent` /
`QidiPrinterAgent` / `SnapmakerPrinterAgent` per [the earlier review
feedback](https://github.com/OrcaSlicer/OrcaSlicer/pull/13752#discussion_r3278574545).
3. **K-series filament profiles** — system profiles for CR-PLA / CR-PETG
/ CR-ABS / CR-Silk / CR-TPU / CR-Nylon / CR-Wood / Hyper PLA / etc. on
K2 / K2 Plus / K2 Pro nozzle sizes (imported from CrealityPrint v7.1.0+,
normalised to OrcaSlicer profile conventions).

The previous stack base (#13291, *CrealityPrint as host type*, by
@imammedo) is **also bundled into this PR** since it's currently
conflicting with main and not moving. Happy to extract it back out if
@imammedo's PR is preferred to land first for attribution — let me know.

## What this PR is *not*

- **No new UI surfaces.** All three features hook into existing UI
(Browse button, Sidebar sync icon, filament dropdowns).
- **No phone-home / telemetry.** No Hark Tech endpoints, no licence
checks, no opt-in dialogs. Pure upstream feature work.
- **No K-series-specific Device tab.** Embedded WebView falls back to
Fluidd/Mainsail on `:4408`, same shape as the existing Moonraker
integration.

## Screenshots

Captured against a K2 Combo (F021, firmware v1.1.260206) on the v4 test
build:

| | |
|---|---|
| ![Discovery
dialog](https://harktech.co.uk/tools/orca-k2/screenshots/orca-k2-discovery-dialog.png)
| **Discovery dialog** — `Browse...` flow on a `host_type=crealityprint`
printer. Click → ~5–10 s LAN scan → K2 found with model + hostname + IP.
|
| ![Filament sidebar populated from
CFS](https://harktech.co.uk/tools/orca-k2/screenshots/orca-k2-cfs-sync-filaments.png)
| **CFS filament sync** — Sidebar after clicking the sync icon: 4 slots
populate with the real loaded CFS spools (3× Hyper PLA + 1× CR-Silk). |
| ![Device tab showing
Mainsail](https://harktech.co.uk/tools/orca-k2/screenshots/orca-k2-device-tab-mainsail.png)
| **Device tab** — Mainsail loaded into the embedded WebView for
`host_type=crealityprint`, mid-print state visible. |

## What's added

### LAN discovery
- **`deps_src/mdns/`** — vendors
[mjansson/mdns](https://github.com/mjansson/mdns) (public domain) plus
Creality's `cxmdns` C++ wrapper from CrealityPrint v7.1.1 (AGPL-3.0,
compatible with OrcaSlicer's AGPL-3.0). Attribution in
`deps_src/mdns/NOTICE.md`.
- **`Utils/CrealityHostDiscovery.{hpp,cpp}`** — synchronous DNS-SD scan
+ per-host `GET /info` probe. Maps model codes `F008` / `F012` / `F021`
→ K2 Plus / K2 Pro / K2.
- **`GUI/CrealityDiscoveryDialog.{hpp,cpp}`** — modal `wxDialog` showing
Model / Hostname / IP for each discovered host.
- **`src/slic3r/CMakeLists.txt`** — adds `Iphlpapi.lib` and `Ws2_32.lib`
to `libslic3r_gui`'s MSVC link line (needed by `GetAdaptersAddresses` +
Winsock2 calls in vendored `mdns.c`).

### CFS filament sync
- **`Utils/CrealityPrintAgent.{hpp,cpp}`** — inherits
`MoonrakerPrinterAgent`, overrides `fetch_filament_info()` to query the
K-series WS protocol on `:9999`, build `AmsTrayData`, and call inherited
`build_ams_payload()`. No printer-specific code lives outside the agent.
- K2 Plus slot-state parser handles the three documented slot states
(`0` empty / `1` manually entered / `2` RFID-tagged) per [DaviBe92's
reverse-engineering docs](https://github.com/DaviBe92/k2-websocket-re).

### K-series filament profiles
- ~110 profile JSONs under `resources/profiles/Creality/filament/`
covering K2 / K2 Plus / K2 Pro × 0.2 / 0.4 / 0.6 / 0.8 nozzle combos ×
CR-PLA / CR-PETG / CR-ABS / CR-Silk / CR-TPU / CR-Nylon / CR-Wood /
Hyper PLA / Hyper PETG-GF / Hyper PLA-CF / etc.
- Imported from CrealityPrint v7.1.0; normalised to OrcaSlicer profile
conventions (tabs not spaces, no `{if !multicolor_method}` wrappers,
`filament_vendor: ["Creality"]` on Creality Generic profiles).

## Tester confirmations on the v4 test build

| Printer | Firmware | Result | Reporter |
|---|---|---|---|
| K2 Pro | v1.1.5.5 / CFS v1.4.2 |  LAN discovery on #13752 test build
|
[@Requiem-MH](https://github.com/OrcaSlicer/OrcaSlicer/pull/13752#issuecomment-4495235225)
|
| K2 Pro | v1.1.5.5 / CFS v1.4.2 |  CFS sync across 1-CFS, 2-CFS,
partial, full configurations |
[@Requiem-MH](https://github.com/OrcaSlicer/OrcaSlicer/pull/13744#issuecomment-4495230061)
|
| K2 Plus | v1.1.5.2 / CFS v1.2.2 |  Slot-state fix resolves the
partial-sync regression |
[@DaviBe92](https://github.com/OrcaSlicer/OrcaSlicer/pull/13744#issuecomment-4499425852)
|
| K2 Plus | v1.1.5.5 / CFS v1.4.2 |  All slots syncing correctly after
fix |
[@swilsonnc](https://github.com/OrcaSlicer/OrcaSlicer/pull/13744#issuecomment-4503273127)
|
| K2 Plus | (Reddit u/TrainAss) |  Both PLA + PETG slots populated
correctly |
[@TrainAss](https://github.com/OrcaSlicer/OrcaSlicer/pull/13744#issuecomment-4503401664)
|
| K1C | (latest stock) |  `boxsInfo` payload format compatible (4 slots
of generic PETG) |
[@JoveYu](https://github.com/OrcaSlicer/OrcaSlicer/pull/13744#issuecomment-4519036448)
|

## Known follow-ups (out of scope)

- **Snapmaker U1 regression**
([@TrainAss](https://github.com/OrcaSlicer/OrcaSlicer/pull/13744#issuecomment-4529350262)):
the v3 build also happened to sync filament from his U1; v4 regressed
this. The refactor only touches `htCrealityPrint`-gated code so this is
likely incidental — needs his config + logs to diagnose. Will follow up
in a separate issue once this lands.
- **Native Device tab for K-series**: deferred. Current Mainsail WebView
shim covers the common case.
- **#13581 (@hamham999) profile overlap**: confirmed minimal code
conflict (zero), profile-file overlap of ~204 files. Whichever PR lands
second rebases off the other.

## Test plan

- [x] Linux build clean on commit `<UPDATED AFTER BUILD>` (LXC 104, GCC
12, cmake)
- [x] MSVC link clean (manual VS 2026 / MSVC 14.51 build)
- [x] End-to-end on real hardware: K2 Combo, K2 Pro, K2 Plus, K1C
- [x] `host_type ≠ htCrealityPrint` paths unchanged — Bonjour fires for
OctoPrint, Flashforge picker fires for Flashforge, Moonraker / Qidi /
Snapmaker agents unchanged
- [x] Profile-validation CI green (was a separate Elegoo test-fixture
failure on main, not introduced by this PR)

Signed-off-by: Igor Mammedov <niallain@gmail.com>
Co-authored-by: Igor Mammedov <niallain@gmail.com>
Co-authored-by: grant0013 <grant@harktech.co.uk>
Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: hamham999 <hamham999@users.noreply.github.com>
Co-authored-by: Alys Andreollo <3528187+alysandreollo@users.noreply.github.com>
2026-06-07 13:16:50 +08:00
SoftFever
ffb55c5710 use DialogButtons 2026-06-07 13:12:51 +08:00
grant0013
e9cf3fa178 harktech: drop hardcoded personal print_host IP from SPARKX i7 presets
The SPARKX i7 machine presets shipped (via #13947) with a contributor's
LAN IP (http://10.10.1.39) hardcoded in print_host. Remove it to match
the K2/K2 Pro/K2 Plus presets, which carry no print_host key — LAN
discovery + the crealityprint agent populate the host at add-printer time.
2026-06-06 23:28:06 +00:00
Alys Andreollo
4f0213d2d7 feat(profiles/Creality): SPARKX i7 to also use new CrealityPrint agent
This updates the SPARKX i7 from a "regular" klipper printer to use the
new CrealityPrinter agent, that talks to it's "supervisor" webserver
instead, and can use the native Creality features: CFS, filament
querying, filament remapping, etc...
2026-06-06 23:27:38 +00:00
Alys Andreollo
5aa3c76c36 feat(CrealityPrint): add support for SPARKX i7 (F022)
Works directly with existing code, just adding the identifiers to the
arrays.
2026-06-06 23:27:38 +00:00
yw4z
97dcae6c6e Troubleshoot Center Dialog (#12416)
* init

* update translations

* clarify warning for rebuild

* auto fill issue field on github with gathered system information

* add detection for linux package type

* fix build errors

* update

* update

* improve reading windows version

* add multi file support for zip and use timestamp on exported zip name

* fix errors

* fix errorz

* fix URL encoding

* fix CPU info

* use home or desktop as default location

* fix distro name on flatpak

* improve detecting local build on linux

* check package type on all platforms

* optimize margins

* improve monitor detection

* add support for adding text file to zip and add system info on export

* exclude user related info from config

* improve exporting profile info

* fix linux monitor info

* update

* update detecting monitor info

* Update TroubleshootDialog.cpp

* Update TroubleshootDialog.cpp

* Update TroubleshootDialog.cpp

* revert scaling and resolution detection for linux

* include project file to zip and notify after saving zip successfully saved

* improve monitor info on mac

* update

* improve packing selection menu

* update

* Update TroubleshootDialog.cpp

* Update TroubleshootDialog.cpp

* Update TroubleshootDialog.cpp

* Update TroubleshootDialog.cpp

* update

* update

* make hash clickable

* fix compatible process counting

* export profiles overview instead copying to clipboard

* auto restart app after cleaning system folder

---------

Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
2026-06-06 23:44:39 +08:00
Chris Djali
e9bc8628fc Fix mDNS on Windows machines with multiple network adapters (#13993)
ws2_32 (Winsock2) and wsock32 (Winsock) are not supposed to be used in the same application.
boost::asio requires ws2_32, but wxWidgets uses wsock32.
Which gets used depends on the order they appear in the link command, as they both define the same symbols, but with different behaviour.
ws2_32 is backwards-compatible with wsock32, so wxWidgets won't be negatively affected by linking with the newer version, and prior to c228ab2da1, that's what happened.
That commit reordered how some libraries were passed to the linker, so swapped the order of these two, breaking mDNS and causing https://github.com/OrcaSlicer/OrcaSlicer/issues/13969
2026-06-06 23:33:51 +08:00
grant0013
4469f009c4 harktech: set host_type + printer_agent=crealityprint on K2/K2 Pro/K2 Plus presets
The CFS-aware filament sync resolves its agent via switch_printer_agent(),
which reads the preset's `printer_agent` field and falls back to "orca" when
unset -- so the K-series presets need printer_agent="crealityprint" (the id
registered by CrealityPrintAgent) in addition to host_type="crealityprint"
(classic PrintHost/LAN-discovery). Without printer_agent the Device-tab sync
defaults to the Orca agent and CFS sync doesn't engage. Set both on the
F008/F012/F021 models that supports_multi_color_print() covers (not K2 SE).
Both keys are in s_PhysicalPrinter_opts, so a new Physical Printer inherits
them from the preset.
2026-06-06 15:01:44 +00:00
Alexander
1bbf8b64f4 Fix Arachne duplicate extrusion caused by bead count mismatch (#14031)
* Add test for Arachne duplicate wall segment detection

Add test cases that reproduce an issue where Arachne generates
duplicate/coinciding extrusion segments at certain min_bead_width settings.

Test configuration:
- Profile: 0.28mm Extra Draft @BBL X1C (0.4mm nozzle, 0.28mm layer)
- outer_wall_line_width: 0.42mm, inner_wall_line_width: 0.45mm
- wall_loops: 2, precise_outer_wall: enabled
- Test polygon: outer rectangle (0,0)-(20,20) with inner cutout (0.5,0.5)-(19.5,19.5)
  This creates a 0.5mm wide frame around the perimeter.

Results:
- 50% min_bead_width (0.20mm): FAILS - detects 4 duplicate segments (all 4 sides)
- 60% min_bead_width (0.24mm): PASSES - no duplicates

At 50%, Arachne generates two separate closed loops that share all 4 edges
of the inner square. At 60%, Arachne generates a single closed loop.

SVG output is exported to /tmp/opencode/ for visual debugging.

* Fix Arachne duplicate extrusion caused by bead count mismatch

WideningBeadingStrategy::compute() used optimal_width (inner wall width)
to determine if a thin wall should produce a single bead. However,
getOptimalBeadCount() uses optimal_width_outer (outer wall width) via
RedistributeBeadingStrategy to decide the bead count.

This inconsistency caused situations where getOptimalBeadCount() returned
2 beads, but compute() produced only 1 bead at full thickness. The single
bead was then generated for both inner and outer contours, resulting in
duplicate extrusion paths.

Fix: Use getTransitionThickness(1) instead of optimal_width. This method
returns the exact threshold for the 1-to-2 bead transition, ensuring
consistency between bead count calculation and bead generation.

Reproduces with: 50% min_bead_width, 0.42mm outer wall, 0.45mm inner wall,
0.5mm polygon inset creating ~0.38mm wall thickness.

Fixes #13917

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-06 22:55:37 +08:00
spr0u
e87625e023 fix: freezing caused by dynamically hidden printer edit button (#14010)
* fix: freezing caused by dynamically hidden printer edit button

* track hover state of individual widgets
2026-06-06 20:39:40 +08:00
hamham999
78eec3a72e Bug fix of Creality K2 Plus profiles start gcode (#14073) 2026-06-06 20:25:58 +08:00
Ioannis Giannakas
9446030079 Linux: opt-in X11 workaround via GDK_BACKEND to enable Device tab HW accel on single monitor setups (#14039)
* Linux compositing - retain old code and make switchable via #if statement

* Make option settable via env variable/.desktop file

* undo accidental empty row delete
2026-06-06 19:09:13 +08:00
SoftFever
69a5a70c9e Merge branch 'main' into harktech/k2-discovery 2026-06-06 17:36:30 +08:00
hamham999
abe6f95aae Updated profiles for all Creality K-series printers (+CFS-c support) and Creality SparkX i7 support (#13947)
* v2 updated profiles and fixes

* Add files via upload

fixing timelaps error

* Add files via upload

fix timelapses

* syntax fix

* fixing profil errors

* Add files via upload

fixing

* Add files via upload

fixing errors

* fixing syntax errors

* Add files via upload

syntax fix

* Add files via upload

naming fix

* Update SPARKX startgcode

* Update SPARKX i7 startgcode

* Update SPARKX i7 startgcode

* Update SPARKX i7 startgcode

* delete old file dublicates

* SparkX gcode fix

* indentation fix

* name changes of printers and deleted 2025 versions

* syntax fix

* fixes

* fixes

* profil name fixes and syntax

* fixed naming restored old names, renamed Ender 3 S1 Sonic, fixed Creality.json - used original formating and fixed the printer list order

* syntax

* Add files via upload

syntax, formating

* differences fix

fixing deleted lines in creality.json

* differences fix in creality.json

* Update Creality CR-10 SE.json

* Update Creality CR-10 V3.json

* Update Creality CR-6 Max.json

* Update Creality CR-10 V3.json

* Update Creality CR-6 SE.json

* Update Creality Ender-3 Pro.json

* Update Creality Ender-3 S1 Plus.json

* Update Creality Ender-3 V3 KE.json

* Update Creality Ender-3.json

* Update Creality Ender-5 Pro (2019).json

* Update Creality K1 Max.json

* Update Creality K1.json

* Update Creality K1C.json

* Update Creality K2 Plus.json

* Update Creality K2 Pro.json

* Update Creality K2.json

* filament and process files formating and fixes form comments

* formating fixes

* 0 value fix

* combine filament profiles

* fix deleted filament

* machine files formatting

* machine files formatting

* Update Creality Hi.json change bed type

* Update bed type Creality Ender-5 Max.json

* chore(profiles/Creality): update SPARKX i7 process names

Update the profile names according to the existing ones, according to
layer height vs nozzle diameter:

- 0.10mm @ 0.2: "HighDetail"
- 0.08mm @ 0.4: "SuperDetail"
- 0.12mm @ 0.4: "Fine"
- 0.16mm @ 0.4: "Optimal"
- 0.20mm @ 0.4: "Standard" (maintained)
- 0.24mm @ 0.4: "Draft"
- 0.28mm @ 0.4: "SuperDraft"
- 0.30mm @ 0.6: "Standard" (maintained)
- 0.40mm @ 0.8: "Standard" (maintained)

This leaves the "HueForge" one untouched, that's it's own setting.

* chore(profiles/Creality): update machine with new process name

Matches the default process for the 0.2 nozzle to the renamed process.

* chore(profiles/Creality): update processes on `Creality.json`

Update the main `Creality.json` manifest with the updated process (file)
names.

* Update Creality K1 Max (0.4 nozzle).json

* Update Creality K1 Max (0.6 nozzle).json

* Update Creality K1 Max (0.8 nozzle).json

* delete cr30 and k1 se naming fix

* Update 0.20mm Standard @Creality K1 SE 0.4.json

* Update Creality.json delete duplicat entry

* fixed formatting

* split PR

* formatting fixes

* settings id instead of filament id delete

* Delete Creality.json in wrong location

* delete filament id

* Update Creality SPARKX i7 0.2 nozzle.json

* Update Creality SPARKX i7 0.4 nozzle.json

* Update Creality SPARKX i7 0.6 nozzle.json

* Update Creality SPARKX i7 0.8 nozzle.json

* delete ender 5 files

* delete ender 5 max files

* adding ender 5 max files

* Update Creality Hi 0.6 nozzle.json

* fixes from comments

* fixes from comments

* fixes from comments and adding filamentid back

* tabs formatting fixes

* fixes

* K1C fixes and printer_structure delete undefined

* formatting

* fixes from comments

* fixes from comments

* use original pictures

* naming fixes, Default Filament profile and Filaments fixed

* match order of key process files

* match order in Creality.json and fix Profile error

* order of printers in menu fix and max accel warning fixed

* revert back fixes for warnings

* fix duplicate keys from resolving merging conflicts

---------

Co-authored-by: Alys Andreollo <3528187+alysandreollo@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-06 17:36:12 +08:00
SoftFever
3a2e3dea43 revert profiles 2026-06-06 17:22:11 +08:00
SoftFever
e23cfd4c7b Merge branch 'main' into pr/grant0013/13752 2026-06-06 17:14:33 +08:00
Kuzuri
94b13120d8 Update Snapmaker U1 (0.4 nozzle).json (#14066)
Made small performance, safety, and readability improvements.

Now the logic is only called to move the build plate lower than the tallest object if the print_sequence is, "by object".

More precise checks to not move the plate beyond the maximum build volume.
2026-06-06 13:49:10 +08:00
packerlschupfer
a529f8c473 Print Host: add Moonraker (Klipper) host type (#13991)
OrcaSlicer currently ships an "Octo/Klipper" host type that maps to the
OctoPrint REST endpoints (api/version, api/files/local). It works for
Klipper setups that run Moonraker with the OctoPrint-emulation plugin,
but native Moonraker — and Moonraker-compatible firmwares like the
Prusa-Firmware-Buddy buddy-klipper fork — speak a different shape:
distinct paths, JSON body for /printer/print/start, {"result":...}
envelope. There's no host type for that today.

Add a new Moonraker class deriving from PrintHost. Endpoints used,
matching the Moonraker spec:

- GET  /server/info                      — connection test, reads
                                            result.klippy_state
- GET  /server/files/roots               — storage-picker dropdown
                                            (returns roots with 'w'
                                            permission); gracefully
                                            degrades if absent
- POST /server/files/upload (multipart)  — upload (form fields:
                                            file, root)
- POST /printer/print/start (json)       — {"filename":"<path>"}; the
                                            filename is whatever the
                                            upload response returned
                                            in result.item.path, so any
                                            server-side rename
                                            (collision suffix etc.) is
                                            respected. JSON body is
                                            built via property_tree
                                            write_json so exotic
                                            characters in the path are
                                            properly escaped.

Auth: X-Api-Key header, only when printhost_apikey is non-empty
(Moonraker can be configured to require it but doesn't by default).
HTTP Basic / Digest are not part of the Moonraker spec and are not
sent.

Storage root is read from upload_data.storage with "gcodes" as the
fallback default, so the existing storage-picker plumbing in
PrintHostDialogs lights up automatically once enumerable roots are
returned.

UI: registers as the "Moonraker (Klipper)" entry under host_type;
selectable via the existing Physical Printer dialog (sidebar's
connection button on the printer card).

Verified against a Prusa-Firmware-Buddy buddy-klipper fork (firmware
identifies as moonraker_version "0.8.0-prusalink-shim"): /server/info
test, multipart upload to /server/files/upload, and JSON
/printer/print/start all work end-to-end. The existing "Octo/Klipper"
entry is left untouched so users currently relying on Moonraker's
OctoPrint-emulation plugin keep working.
2026-06-06 11:12:42 +08:00
Ioannis Giannakas
d5638273c6 Fix row spacing in objects list (#14053)
* Fix row spacing in objects list on MacOS
* Update for windows and linux
* Update row height for a more compact look while maintaining some spacing
2026-06-06 01:23:34 +03:00
foXaCe
57535f30ba i18n(fr): complete French translation to 100% + extract 3DPrinterOS dialog (#14037)
i18n(fr): complete French translation and extract 3DPrinterOS dialog

Translation (localization/i18n/fr/OrcaSlicer_fr.po):
- Translate 222 missing strings and revise 23 fuzzy entries (vouvoiement)
- Quality review of the existing catalog: fix 42 entries
  - "supported" calque -> "pris en charge" (physical sense -> "soutenu")
  - accented capitals (Emission -> Émission, Editer -> Modifier)
  - "cryptage" -> "chiffrement"; "initiés" -> "lancés"; "impacté" -> "affecté"
  - "basé sur" rephrased contextually; ASCII "..." -> "…" in FR strings
- Result: 0 untranslated, 0 fuzzy; placeholders preserved; msgfmt clean

Extraction (3DPrinterOS upload dialog, src/slic3r/Utils/3DPrinterOS.cpp):
- Wrap 9 hardcoded user-facing strings in _L()
- Align the radio-label comparison on _L() so the show/hide logic
  keeps working once the label is translated
- Add the file to localization/i18n/list.txt for gettext extraction
- Add the 7 new msgids to OrcaSlicer.pot and translate them in fr.po

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-06 02:11:00 +08:00
Ioannis Giannakas
1ec574bcbb Fix color swatch not updating when a different coloured filament is selected. (#14038)
* Fix color swatch not updating when a different coloured filament is selected.

* Address review comment
2026-06-06 02:01:51 +08:00
Ian Bassi
9f1bc75c70 Youtube to readme (#14058) 2026-06-05 14:20:27 -03:00
Ian Bassi
f4d34219b8 New FFF Wiki links (#14057) 2026-06-05 11:29:17 -03:00
ExPikaPaka
412eb13239 Retry preset migration when login warmup 401 blocks cloud check (#14049) 2026-06-05 21:25:45 +08:00
Terasit Juntarasombut
c3f8e32aab l10n: Fix contextual translation errors in Thai (th) (#14056)
l10n: Fix contextual translation errors in Thai (th) localization
2026-06-05 21:20:38 +08:00
anjis
1469576dec Fix connection issues with CC2 on Linux (#14024) 2026-06-05 21:15:52 +08:00
ArtytheSecond2nd
72f7b125c6 Add profiles for the Anycubic Kobra S1 Max (#14017)
* Add files via upload

* Delete resources/profiles/Anycubic.json

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Delete resources/profiles/Anycubic.json

* Add files via upload

* Update spacing

* Fix error

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-05 20:59:34 +08:00
Ian Bassi
ea35b9ce68 Divided filament for features (#14042)
* Implement legacy key handling to address possible profile issues following semantic change of per feature filaments

* Reimport base

* Fix inner wall

* Profiles update

---------

Co-authored-by: igiannakas <ioannis@my-spot.co.uk>
2026-06-05 20:45:43 +08:00
Kiss Lorand
6667266b44 Fix tree support interfaces and edge filling (#14040)
Fix tree support interfaces

Fix:
  - non-organic tree support interfaces printing in air
  - organic tree interface missing
2026-06-05 20:39:54 +08:00
Ioannis Giannakas
9c6a3b1054 Fix crash when deleting filaments after a profile switch (#14055) 2026-06-05 14:27:20 +03:00
Ioannis Giannakas
081577fcfa Fix printer variant for voron 0.4 profiles (#14041) 2026-06-05 18:02:23 +08:00
SoftFever
4088a36095 Fixed an random crash on start on Linux (#14052) 2026-06-05 17:00:35 +08:00
Ioannis Giannakas
2ad6beed32 Fix incorrect custom printer profile grouping (#14044) 2026-06-05 00:53:47 +03:00
Ian Bassi
19e8c24f14 Filament for features bump version (#14033) 2026-06-04 12:42:47 -03:00
Robert J Audas
9ede565f62 feat: double-click object list row to frame object in 3D view (#13804)
* feat: double-click object list row to frame object in 3D view

Resolves #13800.

Extends the existing wxEVT_DATAVIEW_ITEM_ACTIVATED handler in
ObjectList::create_objects_ctrl() so that double-clicking an
object / part / instance row calls GLCanvas3D::zoom_to_selection().
This mirrors the existing "Fit camera to scene or selected object"
canvas button, exposed via a natural mouse trigger from the list.
The current view angle is preserved (Blender-style "Frame Selected").

Scope kept intentionally small:
- Object / Part / Instance rows -> zoom_to_selection().
- Filament-color column -> unchanged (still opens color editor).
- Plate rows -> unchanged (no-op).
- Inert in slice-preview mode via get_current_canvas3D(true).

Authored with assistance from Claude (Anthropic).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: no-op object-list double-click in slice-preview mode

Following up on #13800 / #13804. The original guard used
get_current_canvas3D(true)'s `exclude_preview` flag, expecting that to
return nullptr when the preview canvas is active. In fact the flag
falls through to the editor canvas as a default, so the handler was
still calling zoom_to_selection() on the editor canvas — and since the
camera is shared between the editor and preview canvases, the move
was visible in the preview view as the camera jumping to empty world
positions (sliced or excluded, sliced or not).

Replace the misnamed flag with an explicit is_preview_shown() guard
that returns early before any canvas lookup. Manually verified:
preview mode now ignores object-list double-clicks; prepare-mode
behavior unchanged.

Authored with assistance from Claude (Anthropic).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-04 18:23:01 +08:00
Rodrigo Faselli
cf8bb38dc6 Realistic View: Phong Shading + Ambient Oclusion + Cast Shadows (#13704)
* Phong Shading

* Add shader selection to graphics preferences

* SSAO

* 3D canvas menu

Co-Authored-By: yw4z <yw4z@outlook.com>

* better SSAO

* Adjust

* phong in preview mode

* cast shadows

sombra 3

sombra 2

* fix 1

* SSAO independent

* Fix 2

* clean 1

* shadows availables with gouraud

* Update Preferences.cpp

* tweeks

* No Normal textures

* Depth texture allocation

* avoid rebinding/redefining state each render.

* free SSAO

* set shadow fill color

* remove duplicated code

* cached model to avoid per-frame uploads

* yw4z suggestion

Co-Authored-By: yw4z <yw4z@outlook.com>

* Update Preferences.cpp

Co-Authored-By: yw4z <yw4z@outlook.com>

* Update GLCanvas3D.cpp

* only gouraud for preview mode

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Add new OpenGL shading model setting

* Update src/slic3r/GUI/GLCanvas3D.cpp

* Apply suggestion from @RF47

* Title Case

* normal textures

* gamma 2.2

Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com>

* Revert "gamma 2.2"

This reverts commit 909a84af604a080b3f4b8dd67d13956473a77afe.

* Reapply "gamma 2.2"

This reverts commit 0f0c3d9ec0d2c9647ce06afac4fe9266b5ffda97.

* AO blend

Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com>

* Revert "AO blend"

This reverts commit c5c9a3aa6b295704e71299451b937648e5c5f109.

* 4.0 pixel radius

* windows light effect

direccion

brillo

* smoothing

* ajuste de brillo

* ajustes de brillo

* No SSAO  for bed

* disable realistic view on paint gismos

* Update ssao.fs

* circular panel

---------

Co-authored-by: yw4z <yw4z@outlook.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
Co-authored-by: Ian Bassi <12130714+ianalexis@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-04 18:22:06 +08:00
TheLegendTubaGuy
e0a47c61a2 [FIX] Fix Elegoo process profiles (#14020)
* Bump Elegoo profile version to refresh installed bundle

Bumps the Elegoo vendor profile version so installed profile bundles are refreshed from bundled resources after the recent Elegoo profile sync.\n\nThe previous version stayed at 02.04.00.00 after the profile layout changed, so existing installs could keep loading stale system/Elegoo files. That stale bundle can abort during profile loading and cause user presets inheriting from Elegoo machines, such as OrangeStorm Giga and Neptune 3 Max, to report missing parents.\n\nValidation:\n- jq parsed resources/profiles/Elegoo.json\n- verified all Elegoo.json sub_path entries exist\n- git diff --check

* Fix Elegoo process profile manifest
2026-06-04 14:19:10 +08:00
gyarros
a3e479af4e Add FilAr filament vendor (PLA, PLA-mate, PETG) (#13977) 2026-06-04 14:15:28 +08:00
Tobias Gloth
dc5d6b45de add "logfile" option to log diagnostics to file using boost (#13931)
add some logging options

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-04 13:28:51 +08:00
Noisyfox
58a8722a69 Don't show unsupported presets in drop down list (#13959)
* Don't show unsupported presets in drop down list, since it's not useful

* Add option to show unsupported presets

* Explicitly set the default value to `false`

* update filament list without restart on preference change

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-06-04 09:02:09 +08:00
Terasit Juntarasombut
dfe4b52822 l10n: Update Thai translation (th) and fix font issues (#14006)
* i18n: complete Thai (th) localization

* feat: fix thi translation

* feat: fix Thai language localization file

* feat: implement ImGuiWrapper with icon/font support and add Thai localization files

* l10n: Update Thai translation and fix font rendering issues

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-04 02:35:47 +08:00
gedanke
9a053f15eb Fix: macOS Print/Export dropdown dismissed before the cursor reaches the menu (#12936) (#13995)
Since the wxWidgets 3.3 upgrade the Slice/Print split-button's transient
popup was dismissed the moment the cursor entered the gap between the
button and the menu, making "Print -> Export" impossible to select.

Anchor the menu flush against the button (with a 2 px overlap) instead of
6 px below it, removing the dead-zone the cursor had to cross.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-04 02:34:18 +08:00
3DPrinterOS SDK
c467990724 Support for 3DPrinterOS cloud integration (#10403) 2026-06-04 02:31:57 +08:00
Ian Bassi
757b6a5c46 Minor desc and wiki redirection fixe (#14011) 2026-06-03 10:58:25 -03:00
Ian Bassi
6f011c9f6a External bridge fix (#12568)
* Internal bridge fix

Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>

* Revert expansion increasing

Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>

* Recover assert

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update RegionExpansion.cpp

* Update RegionExpansion.cpp

---------

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-06-03 10:19:04 -03:00
Ian Bassi
ae16c76dd2 Bridge Line Width + Improve bridge density (#11255)
* Base

* Standarized

Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>

* Wiki

* Improved descriptions based in RF47 Tests

Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>

* Bridge Flow Wiki

* Removed CMATH

Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>

* Default to 100

* Revert "TESTING BRIDGE DENSITY"

This reverts commit 8634f802311cd3877b0dd5651029b30b2d4eab60.

Removed desc change

* Minor changes

Co-Authored-By: Noisyfox <timemanager.rick@gmail.com>

* Update LayerRegion.cpp

* Missing ;

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>

* Restrict bridge line width to nozzle diameter

* Clarification

* Increased both Bridge Densitys to 125

Co-Authored-By: Valerii Bokhan <80919135+valerii-bokhan@users.noreply.github.com>

* Valerii check

Co-Authored-By: Valerii Bokhan <80919135+valerii-bokhan@users.noreply.github.com>

* Fix error handling

* Clarify thick bridges documentation and tooltips

Updated the documentation and tooltips for 'thick_bridges' and 'thick_internal_bridges' to clarify that bridge extrusion uses a line height equal to the nozzle diameter, and to better explain the trade-offs between strength, reliability, and appearance.

* Partially restore bridge_flow description

* Suggestions

---------

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
Co-authored-by: Valerii Bokhan <80919135+valerii-bokhan@users.noreply.github.com>
2026-06-03 10:16:59 -03:00
Ian Bassi
065540e48f ENH: Relative bridge direction + Align bridge/Ironing angles with model (#12055)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-06-03 10:12:26 -03:00
Kiss Lorand
1b72dbf6fa Fix inconsistent ordering of support base outline and fill (#11761)
* Preserve support base outline/fill order

Honor no_sort when emitting support toolpaths to keep outline-first order.
Group tree support base paths (including lightning) into per-area no_sort collections to prevent interleaving across islands.
Keep lightning layer lookup side-effect free.

* Tag Orca specific changes

Tag Orca specific changes vs. Bambu using the comment //ORCA: . This helps when reviewing merge commits from upstream Bambu so we don't end up causing regressions when pulling in commits from upstream
2026-06-02 14:39:27 +08:00
Kiss Lorand
ac92125012 Fix counterbore hole bridge (#13956)
Fix counterbore hole partial bridge
2026-06-02 14:34:43 +08:00
Bingo2023
d6a49ace15 Update Bambu Lab X2D 0.4 nozzle.json (#13985)
* Update Bambu Lab X2D 0.4 nozzle.json

corrected mistake from 31.5.2026
" is correct in machine code.

* Update Bambu Lab X2D 0.4 nozzle.json

fixed error with " -> \"

* Merge branch 'main' into patch-2
2026-06-01 22:15:34 +08:00
yw4z
71eebc2332 Merge code base of Setup Guide and Standalone versions of Printer / Filament Selection Dialogs (#13579) 2026-06-01 21:00:42 +08:00
Ian Bassi
7a0c149701 lightning infill angles Wiki (#13984) 2026-06-01 09:24:00 -03:00
Alexandre Folle de Menezes
737c684a93 Improve and complement pt-BR translation (#13973) 2026-06-01 09:12:06 -03:00
Terasit Juntarasombut
91ce821959 i18n: Complete Thai (th) localization and enable Thai in GUI (#13916)
* i18n: complete Thai (th) localization

* feat: fix thi translation

* feat: fix Thai language localization file

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-01 18:11:14 +08:00
Kiss Lorand
752551292b Fix modifiers bridge speed not respected (#13971)
* Respect modifiers bridge speed
2026-06-01 16:00:00 +08:00
Calcousin55
86ad4d141a Preserve Z scale and apply correct offset when Ctrl‑dragging XY corner (#13840) 2026-06-01 00:16:06 +08:00
Vovodroid
30e83d790c Enable wipe with firmware retractions is set 100% before wipe. (#13812)
Enable wipe with firmware retractions
2026-06-01 00:14:42 +08:00
LH
7f7e7dff3a Update LH Stinger profile (#13865)
- Adding default speed for the new initial_layer_travel_acceleration
2026-06-01 00:13:35 +08:00
Rodrigo Faselli
8548e5ca96 Expose lightning infill angles (#13848)
* expose lightning infill angles

Update PrintObject.cpp

* Update src/libslic3r/PrintConfig.cpp

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Update GUI_Factories.cpp

* Fix lightning infill angles to 45 degrees for supports

Updated lightning infill angles to fixed values for consistency.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-01 00:12:49 +08:00
anjis
4d05ba0d02 Support GIGA multi-printhead configuration options (#13901)
* Support GIGA multi-printhead configuration options

* Removed unused G-code comments.

* Added parallel_printheads_count option to configure parallel printhead count.

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-01 00:11:07 +08:00
Noisyfox
3cce9b09ed Cut: Make sure x offset is considered when checking multi dovetail (#13945)
Make sure x offset is considered when checking multi dovetail (OrcaSlicer/OrcaSlicer#13940)
2026-05-31 22:34:32 +08:00
Matias Fernandez
8362eba19d Remove redundant toolchange retraction/un-retraction during filament change in CC1 profile (#13456)
* Fix: Disable redundant toolchange retraction for Elegoo Centauri Carbon
Sets `retract_length_toolchange` to 0 in the Elegoo Centauri Carbon (ECC) machine profile.
This resolves an issue where a massive filament blob would form on the prime tower immediately after resuming a manual filament change (M600). The blob was caused by a conflict between OrcaSlicer's default toolchange logic and Elegoo's hardcoded firmware behavior:
- Elegoo's firmware (specifically the `cmd_PAUSE` and `cmd_RESUME` sequences) completely takes over pressure management during an M600. It performs its own initial 2mm retraction, a 120mm purge, and a silicone brush wipe, returning the print head to the prime tower perfectly primed.
- Previously, Slicer was unaware of the firmware's priming and would issue a redundant 2mm un-retract (`G1 E2`) upon resume. Forcing 2mm of filament out of an already-full nozzle created the blob.
By disabling the toolchange retraction (`0`), Slicer correctly hands off filament pressure management during an M600 entirely to the Elegoo firmware, preventing double-retractions and eliminating the blob.

* fix errors after merging main

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-31 20:35:35 +08:00
yw4z
1644d49ae1 Fix non functional OBJ import dialog on linux (#13914) 2026-05-31 20:16:29 +08:00
Kiss Lorand
3a8dfeaa08 Preview sliders overhaul (#13919)
* Make preview slider labels draggable

Add label hit testing and delta-based dragging for the vertical preview slider labels. Keep label drags tied to the selected handle, prevent slider hover/timeline/menu handling from stealing label interactions, and keep value setters from changing the active selection implicitly.

* Refresh preview slider visuals

Update preview slider rails, handles, and labels for the refreshed light and dark theme appearance. Apply the same visual language to the horizontal slider, align single-layer and multi-layer labels, and remove obsolete triangle label geometry.
2026-05-31 18:55:14 +08:00
Bingo2023
c714935596 Update Bambu Lab X2D 0.4 nozzle.json - updade Maschine Startcode (#13944)
* Update Bambu Lab X2D 0.4 nozzle.json - updade Maschine Startcode

bump in changes from BBL

update:
===== 2026/05/08 =====

old:
===== 2026/03/26 =====

* Update Bambu Lab X2D 0.4 nozzle.json

corrected:
- " " -> ' '
- {Tab} -> deleted

* Update Bambu Lab X2D 0.4 nozzle.json

replaced " -> '
2026-05-31 18:46:46 +08:00
SoftFever
372f7823ac Feature/tweak stealth mode (#13963)
* Update the stealth mode description to reflect the current code changes in 2.4.

* disable HMS if bambu network plugin is not installed or in stealth mode

* fix build err

* add hide_login_side_panel to control whether to show login panel in home page
2026-05-31 18:04:06 +08:00
Ian Chua
535911fcfe fix: 409 conflicts resolution in notifications (#13900)
* fix: 409 conflicts resolution in notifications

* fix: silently log other http errors

* fix: pass force push flag to start_sync_user_preset

* remove formatting churn

* fix: propagate force push down put_setting

* refactor render_hyperlink_action to PopNotification for reuse

* fix an issue that hold status should be cleared before force pushing.

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-31 16:23:10 +08:00
Kiss Lorand
6a26284ba6 Fix air filtration gcode emitted even if not not supported by the printer (#13868)
* Fix air filtration gcode emitted even if not not supported

- do not emit air filtration gcode if not supported by the printer
- removed redundant  "add_eol" parameter from "set_exhaust_fan()" function
2026-05-31 12:11:40 +08:00
SoftFever
b78d5b94dc Add SeeMeCNC printer profiles (#13924)
* Add SeeMeCNC printer profiles

Adds machine, filament, and process profiles for SeeMeCNC printers:
Artemis, BOSSdelta 300, BOSSdelta 500 (0505/0510/0521), and
RostockMAX v3.2/v4 — covering 0.4mm, 0.5mm, 0.7mm, and 1.0mm nozzles.
Includes PLA, ABS, PETG, PETG-CF, PA-CF, and TPU filament profiles.

Co-authored-by: Sam <47287571+Sewbot@users.noreply.github.com>

* bump version

---------

Co-authored-by: Sam <47287571+Sewbot@users.noreply.github.com>
2026-05-30 22:06:54 +08:00
Wegerich
61e2abfb2b Enhance G92 E0 case sensitivity check (#13933) 2026-05-30 21:57:54 +08:00
Tobias Gloth
9d8c7cc495 use /bigobj when building Eigen with MSVC (#13922) 2026-05-30 16:51:31 +08:00
Ian Bassi
9a01df4a80 Filament for features for SEMM (#13937)
* Remove limit for SEMM

* Remove legacy check

* Revert "Remove legacy check"

This reverts commit c6e5074114.
2026-05-30 14:16:59 +08:00
Kiss Lorand
83946f3df8 Fix division by zero in organic tree support generation (#13936) 2026-05-29 18:09:47 -03:00
grant0013
8a2dbd1c10 Fix merge-resolution braces in PrintHostDialogs.hpp + PhysicalPrinterDialog.cpp
The 8dfd480c52 merge stripped <<<<<<<, =======, >>>>>>> markers
from three files. Two of the resolutions ended up syntactically
broken:

* PrintHostDialogs.hpp: both sides of the conflict added a new class
  declaration in the same spot. The common closing }; lived after
  the >>>>>>> marker, so concatenating without re-adding a closing
  brace between the two classes left CrealityPrintHostSendDialog
  unclosed. Compiler caught it via "storage class specified for
  EVT_PRINTHOST_PROGRESS" at the wxDECLARE_EVENT lines (the events
  were being parsed inside the still-open class body).

* PhysicalPrinterDialog.cpp: upstream's BonjourDialog fallback was
  inside an `else { }` block whose closing brace remained after I
  replaced the else with explicit early-return branches. Result was
  one extra `}` after the Bonjour block.

No public commits affected -- the broken merge commit is still
local-only on LXC 104. Catching this before the force-push.
2026-05-28 08:10:21 +00:00
grant0013
8dfd480c52 Merge branch 'upstream/main' into harktech/k2-discovery
Consolidates #13744 (CFS filament sync) into this PR per maintainer
request and resyncs with main. Conflicts resolved:

* src/slic3r/GUI/Plater.cpp -- parallel else-if added by both sides
  (htCrealityPrint + flashforge_local_api branches); kept both.
* src/slic3r/GUI/PhysicalPrinterDialog.cpp -- both sides added a
  host-specific Browse dialog (Creality DNS-SD + Flashforge); kept
  both as early-return branches, fall through to BonjourDialog.
* src/slic3r/GUI/PrintHostDialogs.hpp -- parallel class declarations
  (CrealityPrintHostSendDialog + FlashforgePrintHostSendDialog);
  kept both.
2026-05-28 07:56:06 +00:00
Grant Harkness
7c60a678cf harktech: move CFS sync into CrealityPrintAgent::fetch_filament_info
Address SoftFever review item 1 on #13752: CFS sync now lives in
CrealityPrintAgent::fetch_filament_info, matching the MoonrakerPrinterAgent
/ QidiPrinterAgent / SnapmakerPrinterAgent shape. The standard

  Sidebar::sync_ams_list
    -> DeviceManager::get_selected_machine()
    -> Sidebar::load_ams_list(obj)
    -> Sidebar::build_filament_ams_list(obj)
    -> agent->fetch_filament_info(dev_id)
    -> build_ams_payload(box_count, max_lane_index, trays)

path now applies to CrealityPrint hosts identically to the other
Klipper-flavoured agents. CrealityPrintAgent inherits
MoonrakerPrinterAgent, so the inherited connect_printer() ->
announce_printhost_device() -> SSDP callback already triggers
MachineObject creation in DeviceManager's localMachineList for
K-series LAN hosts; the Plater-side special case was simply bypassing
get_selected_machine() before it could fire.

Removed:
* The if (host_type == htCrealityPrint) block in
  Sidebar::sync_ams_list (and the CrealityPrintAgent.hpp include it
  required).
* The static CrealityPrintAgent::sync_filaments_into_ams_list()
  helper that wrote directly to PresetBundle::filament_ams_list,
  plus the CFSAmsListResult status struct that surfaced dialog text
  back to the Sidebar.

CrealityPrintAgent::fetch_filament_info itself is unchanged - it was
already calling the inherited build_ams_payload() correctly.

Net diff: 227 deletions / 4 insertions across 3 files. No behaviour
change. Discovery + WS protocol parsing unchanged.
2026-05-23 15:49:46 +00:00
Noisyfox
b142da72d8 Merge branch 'main' into dev/extruder-toggle 2026-05-22 19:26:48 +08:00
Noisyfox
2ec8aa7b6c Fix more calibration 2026-05-22 15:34:55 +08:00
Noisyfox
0b3f5050b0 Fix issue that notification shown as empty if message contains non-ascii characters 2026-05-22 11:10:09 +08:00
Noisyfox
a5453acdc5 Fix wrong multi-variant category displayed in unsaved cfg dialog if option name is a prefix of another option
(such as `filament_wipe` and `filament_wipe_distance`)
2026-05-22 10:46:29 +08:00
Noisyfox
6690bdf149 Fix calibration
Co-authored-by: zhimin.zeng <zhimin.zeng@bambulab.com>
2026-05-21 23:40:51 +08:00
grant0013
3add401274 harktech: repoint CR-PETG K2 profiles at fdm_filament_pet base
The fdm_filament_petg base template added in 7f46c652 (cherry-picked
from hamham999's PR 13581) had two bugs:
  - filament_type: ["PLA"]  (should be PETG)
  - inherits: null          (should chain to fdm_filament_common)
so OrcaSlicer still rejected our CR-PETG profiles even after the
base was installed.

The existing fdm_filament_pet base already declares filament_type:
["PETG"] and inherits fdm_filament_common, so it correctly serves as
the PETG base. Repoint all 10 K2-family CR-PETG profiles at it and
remove the buggy fdm_filament_petg.json + its Creality.json entry.

This effectively reverts the resource addition from 7f46c652 while
keeping the CR-PETG filament profile additions from 5612e120 (now
with a working inherits chain).

Verified locally: OrcaSlicer parses all 10 profiles without error and
the K2 family populates the Add Printer dialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:32:52 +00:00
grant0013
454165b375 harktech: add fdm_filament_petg base template for CR-PETG profiles
The 10 CR-PETG profiles added in 5612e120 (also from hamham999s PR
13581) inherit from fdm_filament_petg, but that base template did
not exist in the Creality profile set - only fdm_filament_pet. Result:
OrcaSlicer logged
  can not find inherits fdm_filament_petg for CR-PETG @Creality K2...
and aborted the rest of the Creality filament load, leaving the
filament dropdown empty for K2 users.

Adds the base template (originally added in hamham999s PR 13581) and
registers it in Creality.json filament_list after fdm_filament_pet.

Reported-by: local testing on Hark Tech 2026-05-21 test build.

Co-Authored-By: hamham999 <hamham999@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:25:01 +00:00
grant0013
b513dff212 harktech: add CR-PETG K2 profiles + fix HF Generic vendor categorization
Two follow-ups from #13744 review:

1. K2 firmware payloads reference "CR-PETG" by name (per DaviBe92's
   reverse-engineering work in k2-websocket-re, and a real Creality spool
   seen by @swilsonnc), but the profile was missing from the Creality
   filament set. Adds 10 K2-family variants:
     - K2: 0.4, 0.6, 0.8 nozzles
     - K2 Plus: 0.2, 0.4, 0.6, 0.8 nozzles
     - K2 Pro: 0.4, 0.6, 0.8 nozzles
   Profile values come from CrealityPrint v7.1.0 via @hamham999's
   parallel work in OrcaSlicer/OrcaSlicer#13581. Files re-indented with
   tabs and BOM stripped to match repo convention.

2. Creality HF Generic PLA and Creality HF Generic Speed PLA were missing
   filament_vendor: ["Creality"] so they appeared under "General" rather
   than "Creality" in the filament selector.

Reported-by: swilsonnc, DaviBe92 (CR-PETG missing)

Co-Authored-By: hamham999 <hamham999@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 09:21:39 +00:00
Noisyfox
bb9bc04996 Merge branch 'main' into dev/extruder-toggle 2026-05-21 15:42:38 +08:00
Noisyfox
246d54a6bf Partial fix of accl & jerk check 2026-05-21 15:21:57 +08:00
Noisyfox
15952edf4d Fix wipe_speed 2026-05-21 13:30:05 +08:00
Noisyfox
8a6573db19 Fix usage of most of the multi-variant parameters 2026-05-21 13:15:33 +08:00
SoftFever
5b59724938 Merge branch 'main' into harktech/k2-discovery 2026-05-21 11:43:52 +08:00
Noisyfox
353e9519f0 Fix nozzle icon render for certain multi-variant configs 2026-05-21 09:04:27 +08:00
Noisyfox
1c8c7820c8 Initial attempt to make speed configs multi-variant
Co-authored-by: chunmao.guo <chunmao.guo@bambulab.com>
Co-authored-by: zhimin.zeng <zhimin.zeng@bambulab.com>
Co-authored-by: qing.zhang <qing.zhang@bambulab.com>
2026-05-21 09:04:27 +08:00
Noisyfox
e87b0d57b4 Make inner_wall_speed multi-variant 2026-05-21 09:04:27 +08:00
Noisyfox
7a992b397b Bring back flow variant combo 2026-05-21 09:04:26 +08:00
Noisyfox
e785f89833 Add extruder sync button
Co-authored-by: chunmao.guo <chunmao.guo@bambulab.com>
2026-05-21 09:04:26 +08:00
chunmao.guo
8f2ff7447e ENH: param multiple extruder indicator icon
Change-Id: I9e382dc72deaa0b682b1474c9b4094c31e550ad8
Jira: STUDIO-8872, STUDIO-9213
(cherry picked from commit dba496e1cc9cb293f63f950dd02f94e27b72fda1)
2026-05-21 09:04:26 +08:00
Noisyfox
25c0cf2943 Fix missing extruder switch in printer config 2026-05-21 09:04:26 +08:00
Noisyfox
59c7809ee1 Make sure settings tab are still marked as modified even if modified variant is not currently selected 2026-05-21 09:04:26 +08:00
Noisyfox
caa5bb38ea Fix issue that dirty status not updated when switching between variants/extruders 2026-05-21 09:04:26 +08:00
Noisyfox
78daf90c95 Show extruder switch in motion ability page 2026-05-21 09:04:26 +08:00
grant0013
c27339dac5 harktech: parse K2 Plus CFS slot state values (1=selected, 2=loaded)
Root-cause + fix for the K2 Plus sparse-slot / wrong-slot-mapping bug
reported by multiple testers on Reddit and PR #13744 (DaviBe92, Psych0SW,
swilsonnc, TrainAss, Gullible-Price-4257).

DaviBe92 supplied a raw /box/getRealBoxesInfo payload from a K2 Plus
running printer FW 1.1.5.2 / CFS 1.2.2 that shows the K2 Plus uses
THREE state values for slots:

  state: 0 → empty
  state: 1 → loaded AND currently selected as the active spool
  state: 2 → loaded but not currently selected

K2 (base) and K2 Pro firmwares — confirmed against the maintainer's
test printer — only use 0/1. Our parser had assumed the 0/1 form and
filtered with `if (state != 1) continue`, dropping every state=2 slot.

Symptoms this explains:
* DaviBe92: 3 spools loaded, only 1 displayed (the state=1 slot).
* Psych0SW / swilsonnc: 2 of 4 slots returned, "2nd shows 3rd's data"
  — the parser dropped slots with state=2, leaving a sparse map that
  PresetBundle::sync_ams_list then packs into consecutive UI trays.
* TrainAss / Gullible-Price-4257: "No loaded slots detected" — likely
  the same root cause when zero slots happen to be state=1.

Fix: treat any non-zero state as loaded. Belt-and-braces: also skip
entries that are blanked-out (vendor and type both empty) regardless of
state, in case a future firmware uses yet another encoding for empty.

No change required for K2 / K2 Pro behaviour — they already only emit
state=0 (empty) or state=1 (loaded), and the new filter accepts both.
2026-05-20 12:19:15 +00:00
grant0013
3df71d86aa harktech: normalize Creality K2 filament profiles per repo convention
CI flagged ~150 K2 profile files for using space indentation; repo convention
is tab indentation. Ran the upstream-provided fixer:

    python3 scripts/orca_filament_lib.py -v Creality -p filament -f --force

Mechanical normalization:
- Spaces → tabs (1 tab per indent level)
- Field ordering normalized (name + type first)
- Single-value scalar fields converted to single-element arrays where the
  schema expects arrays (filament_cost, filament_density,
  temperature_vitrification, filament_max_volumetric_speed)

No semantic content changes. Pre-existing issue from the original K2
profile import; touching these files in the multicolor_method strip
brought them into the validator's PR-diff scope.
2026-05-20 11:27:29 +00:00
grant0013
32cf78d0e6 harktech: move CFS filament sync into CrealityPrintAgent
Per @SoftFever review on #13752, printer-specific filament sync logic
belongs in the agent rather than in Sidebar. This consolidates the
previously-duplicated code so all CFS-specific work lives in
CrealityPrintAgent.

Changes:
- New: CrealityPrintAgent::sync_filaments_into_ams_list() — static method
  that builds a CrealityPrint host from a printer_cfg, queries CFS slots,
  populates PresetBundle::filament_ams_list, and triggers sync_ams_list().
  GUI-free; returns a result struct (Status + counts + detail) so the
  caller decides what dialog to show.
- New: nested CFSAmsListResult struct describing the five possible
  outcomes (Success / NotCfsCapable / QueryFailed / EmptySlots / NoMatches).
- Removed: Sidebar::sync_filaments_from_creality_cfs() entirely (its body
  is now the agent method).
- Plater.hpp loses the declaration; Plater.cpp dispatches to the agent
  inline within sync_ams_list() and owns only the dialog + post-sync UI
  refresh (combo updates, layout, preset selection, persistence).

Two CFS-related entry points on the agent now coexist:
- fetch_filament_info() — agent-driven path; publishes via AmsTrayData
  and build_ams_payload(). Active when a MachineObject is bound (BBL
  concept, not currently created for Creality LAN hosts).
- sync_filaments_into_ams_list() — explicit-pull path used today by the
  Sidebar's "Sync filaments" button until the K-series MachineObject
  work catches up.

No user-visible behaviour change — same end-to-end flow, the data work
just lives in the agent now.
2026-05-20 11:17:06 +00:00
grant0013
d643701529 harktech: move vendored mdns into deps_src/ per repo convention
Per @SoftFever review on #13752, third-party vendored libraries belong in
deps_src/ alongside expat, imgui, hidapi, etc.

- All 5 files (mdns.{h,c}, cxmdns.{h,cpp}, NOTICE.md) move from
  src/slic3r/Utils/mdns/ to deps_src/mdns/.
- deps_src/mdns/CMakeLists.txt builds mdns as a STATIC library and scopes
  the MSVC Iphlpapi/Ws2_32 link requirement to that target instead of
  libslic3r_gui's global MSVC block.
- deps_src/CMakeLists.txt gains add_subdirectory(mdns).
- src/slic3r/CMakeLists.txt drops the inline source listings and links
  libslic3r_gui against the new mdns target; MSVC block keeps only
  Setupapi.lib.
- src/slic3r/Utils/CrealityHostDiscovery.cpp #include updated to use the
  include dir exposed by the new mdns target.

Verified by a clean Linux build (orca-slicer links successfully).
2026-05-20 11:16:13 +00:00
grant0013
7349464392 harktech: fix Creality filament profile bugs from Reddit feedback
Two data-only fixes:

#1 Strip undefined {if !multicolor_method} wrapper from filament_start_gcode on 110 K2-Plus profiles. The placeholder is a CrealityPrint-ism that survived the profile port; Orca has no such variable, so slicing failed with a hard parser error (reported on Hyper PLA by u/Gullible-Price-4257). Inner per-layer temp logic now runs unconditionally, which is the correct default.

#4 Add filament_vendor: [Creality] to 50 Creality Generic profiles missing the field. Without it the UI grouped them under Generic instead of Creality (reported by u/mharrop94).
2026-05-20 08:50:52 +00:00
Grant Harkness
dcb2de37c6 Merge branch 'OrcaSlicer:main' into harktech/k2-discovery 2026-05-19 21:05:55 +01:00
grant0013
598df9ff0b Add Creality K-series LAN discovery via DNS-SD mDNS
When a user adds a Creality K-series printer (host_type=crealityprint)
and clicks the existing "Browse" button in the Physical Printer dialog,
dispatch to a new CrealityDiscoveryDialog that finds K2 / K2 Plus /
K2 Pro printers on the LAN automatically. For other host types the
button keeps its existing BonjourDialog behaviour.

  CrealityHostDiscovery (src/slic3r/Utils/CrealityHostDiscovery.{hpp,cpp})

    Wraps the vendored cxmdns wrapper from the previous commit:

      static std::vector<CrealityHost> scan(bool probe_info = true);

    Calls cxnet::syncDiscoveryService({"Creality", "creality"}) to find
    K-series printers via DNS-SD, dedupes by IP, then optionally HTTP
    GETs http://<ip>/info on each match to fetch the model code
    (F008 = K2 Plus, F012 = K2 Pro, F021 = K2) and MAC. Returns enriched
    {ip, hostname, model_code, model_name, mac, cfs_capable} entries.

  CrealityDiscoveryDialog (src/slic3r/GUI/CrealityDiscoveryDialog.{hpp,cpp})

    Modal dialog with a wxListView showing Model / Hostname / IP per
    discovered host. Runs CrealityHostDiscovery::scan() synchronously
    with wxBusyCursor + wxWindowDisabler (5-10s total wait). User picks
    one, dialog returns the IP via selected_ip().

  PhysicalPrinterDialog (src/slic3r/GUI/PhysicalPrinterDialog.cpp)

    The "Browse" button's click handler now reads host_type from the
    edited config. If htCrealityPrint, opens CrealityDiscoveryDialog
    and writes "http://<ip>" into the print_host field. Otherwise the
    existing BonjourDialog path runs unchanged -- no behaviour change
    for OctoPrint / Moonraker / Klipper users.

No new UI surface: one existing button now does the right thing per
host_type, mirroring how Creality Print discovers its own printers.
2026-05-19 20:03:54 +00:00
grant0013
d6cffbba26 Vendor mjansson/mdns + cxmdns wrapper for DNS-SD discovery
Drop a public-domain mDNS / DNS-SD lookup library into the tree at
src/slic3r/Utils/mdns/. Two pieces:

  mdns.{h,c}    -- public-domain library by Mattias Jansson from
                   https://github.com/mjansson/mdns
  cxmdns.{h,cpp} -- C++ wrapper from CrealityOfficial/CrealityPrint
                    v7.1.1 (AGPL-3.0, compatible with OrcaSlicer's
                    AGPL-3.0)

cxmdns exposes one function:

  std::vector<machine_info> cxnet::syncDiscoveryService(
      const std::vector<std::string>& prefix);

It sends a DNS-SD meta-discovery query (_services._dns-sd._udp.local.),
listens ~5 seconds, and returns {ip, service_name} for every service
announcement whose name contains any of the given prefixes.

Motivation: Creality K-series firmware announces each printer under a
per-device-unique service type _Creality-<MAC-derived-hex>._udp.local.,
so OrcaSlicer's existing Bonjour code (which queries fixed service
names like _octoprint._tcp) cannot find them. The DNS-SD meta-browser
approach implemented here is the standard way to discover services
when you do not know their exact names in advance.

mdns.c calls GetAdaptersAddresses (iphlpapi) and Winsock2 functions,
neither of which were on libslic3r_gui's MSVC link line; both are
added here so the vendored sources compile and link standalone.
Attribution captured in src/slic3r/Utils/mdns/NOTICE.md. No callers
yet; the K-series discovery class + UI lands in the next commit.
2026-05-19 20:03:53 +00:00
grant0013
e7283fab2c CrealityPrintAgent: prefer system bases over user copies in CFS matcher
The matcher tiebreaker previously preferred user-edited filament
presets over system bases on a tied score. For a K2 owner who has
a custom copy of Creality Generic PLA @K2-all called eg
Creality Hyper PLA @K2 (mine), the matcher scored both that copy
and the shipped brand-specific Hyper PLA @Creality K2 0.4 nozzle
at 30, then tiebreak picked the user copy. User copies inherit
filament_id from their parent -- in this case the generic PLA
GFL99 -- so the returned id pointed at Generic PLA, not at
Hyper PLA brand-specific id (01001). PresetBundle::sync_ams_list
then resolved by id back to Creality Generic PLA @K2-all, visibly
losing the brand on every sync.

Flip the tiebreaker to prefer system over user. The shipped
brand-specific preset always wins now and sync_ams_list lands on
the right slot label.

Drop the post-sync user-override step from the sidebar path that
was layered on to compensate -- silently substituting the user
local tuning is the wrong default for an upstream-shipped
feature; users who want their local tuning on a synced slot still
get to it via the existing combo dropdown.
2026-05-19 16:01:14 +00:00
grant0013
3af72ab54d Creality: import K2/K2 Plus/K2 Pro filament profiles from CrealityPrint
Import 191 brand-specific filament presets for the K2 family of
printers (K2, K2 Plus, K2 Pro), lifted from
CrealityOfficial/CrealityPrint v7.1.1 under AGPL-3.0 (compatible
with OrcaSlicer AGPL-3.0).

Brand coverage:
  CR-series:    CR-PLA, CR-PLA Matte, CR-PLA Fluo, CR-Silk,
                CR-TPU, CR-ABS, CR-Nylon
  Hyper-series: Hyper PLA, Hyper PLA-CF, Hyper ABS, Hyper PA-CF,
                Hyper PA6-CF, Hyper PAHT-CF, Hyper PA612-CF,
                Hyper PC, Hyper Marble, Hyper Stardust,
                Hyper L-W PLA, Hyper PPA-CF
  Third-party:  eSUN PLA+ / PLA-HS / PLA-Matte / PLA-Silk /
                PLA-CF / PLA-LW / PLA-Lite, PolySonic PLA /
                PLA Pro, Panchroma PLA Matte / Satin, Soleyin
                Ultra PLA, HP Ultra PLA, HP-ASA, HP-TPU
  Ender PLA variants and CrealityPrint per-K2 Generic versions of
  PLA, ABS, ASA, PA, PA-CF, PAHT-CF, PET, etc.

This closes the matching gap exposed by the CFS filament-sync
work. When the K2 reports spool brand Hyper PLA or CR-PLA, the
existing Creality vendor only had a generic Creality Generic PLA
@K2-all preset to fall back to, so per-brand temps / PA / cooling
tuning was lost on every sync.

Schema and naming are drop-in compatible -- CrealityPrint
references the K2/K2 Plus/K2 Pro machine names already present in
OrcaSlicer Creality vendor (Creality K2 0.4 nozzle, etc), so no
machine-side changes or compatible_printers rewrites are required.

K2 SE filaments and the PETG/PP/PPS/HIPS family are deferred. K2
SE machine profile is not yet in OrcaSlicer Creality vendor, and
the PETG/PP/PPS/HIPS family relies on fdm_filament_* base
templates that do not exist in Orca Creality vendor -- importing
those bases from CrealityPrint did not make them register as valid
parents at preset-load time. Coverage for the missing families
will land in a follow-up once the loader requirements for new
bases are understood.

Vendor version bumped to 02.03.02.75 to trigger the per-user
profile refresh on next launch. Source attribution captured in
resources/profiles/Creality/NOTICE.md.
2026-05-19 16:01:14 +00:00
grant0013
251f5faefa Add Sidebar::sync_filaments_from_creality_cfs explicit-pull path
The agent fetch_filament_info() path does not fire for Creality
K-series hosts because Sidebar::build_filament_ams_list()
short-circuits when no MachineObject is bound. MachineObject is a
BBL cloud-connected-printer concept that does not apply to LAN
Moonraker-style hosts like the K2 -- the AMS-sync icon click was a
no-op for them.

Mirror what Creality Print own slicer does (explicit Auto Mapping
button bypassing the BBL plumbing): when the user clicks the
existing AMS-sync icon and host_type=crealityprint, dispatch to a
new Sidebar::sync_filaments_from_creality_cfs() that reads the
active printer host config, confirms the printer is a CFS-capable
K-series board, queries boxsInfo over the printer WS on port 9999,
scores each loaded slot against the user filament presets and
builds a filament_ams_list entry with the matched filament_id,
colour and slot indices, then routes through
PresetBundle::sync_ams_list so the filament combo widgets get the
same rebuild as BBL printers and runs the BBL post-sync refresh
sequence (on_filament_count_change + combo update + select_preset
+ export_selections + update_dynamic_filament_list).

No new UI surface -- the existing AMS-sync icon does the right
thing per host_type. Match-and-resolve logic is hoisted out of the
agent anonymous namespace into public statics so the sidebar can
call it without duplicating scoring rules.
2026-05-19 16:01:14 +00:00
grant0013
7514cf4012 CrealityPrint: default Device-tab WebView URL to Mainsail on :4408
K-series printers (K2, K2 Plus, K2 Pro) ship with Mainsail on port
4408. Port 80 hosts only the Creality control/upload API, which
returns 404 for unknown paths and renders as a blank/404 page in
Orca Device tab.

Override CrealityPrint::get_print_host_webui() to default to
http://<host>:4408/ when the user has not explicitly set
print_host_webui, giving K-series owners a complete printer
dashboard in the Device tab out of the box.
2026-05-19 16:01:13 +00:00
grant0013
a4ea6c6fde CrealityPrintAgent: match Orca filament presets by vendor + brand name
Score visible compatible filament presets against the CFS spool
(vendor, brand_name, type) tuple to pick the right preset:

  +20  preset name contains the brand_name as a substring
       (eg Hyper PLA in Hyper PLA @Creality K2 0.4 nozzle)
  +10  preset name contains the vendor substring (eg Creality)

Requires preset.filament_type to equal the spool base type so a
PETG preset is never auto-picked for a PLA spool. Falls back to
filaments.filament_id_by_type(base_type) when nothing scores.

Considers both base/system presets and user-derived copies -- K2
owners frequently keep tweaked copies of system presets (per-spool
PA, temps), so filtering to bases-only would skip exactly the
presets users care about most.
2026-05-19 16:01:13 +00:00
grant0013
d1a33d2efd Add CrealityPrintAgent for CFS filament sync
Subclass of MoonrakerPrinterAgent for K-series Creality printers
(K2, K2 Plus, K2 Pro) with CFS support. Registers in
NetworkAgentFactory under host_type=crealityprint. Overrides
fetch_filament_info() to defer to base when the host is not a
CFS-capable K-series board, otherwise query the boxsInfo WebSocket
on port 9999, parse the box hierarchy into CFSSlot[], and publish
each loaded slot as an AmsTrayData entry via build_ams_payload()
so they surface in Orca filament UI.

boxsInfo schema reference (verified against K2 Combo F021 firmware
v1.1.260206):
  materialBoxs[].materials[] fields: id, state, vendor, type, name,
  color, pressure, rfid, percent. state=1 means loaded; box.type=0
  is a CFS unit, type=1 is the external spool holder (handled by
  the upload dialog).

Accepted CFS boxes are renumbered sequentially since the K2 raw
box.id has gaps for the external spool holder. Model detection is
delegated to CrealityPrint::supports_multi_color_print() added in
PR #13291.
2026-05-19 16:01:13 +00:00
Igor Mammedov
4e4dd708b9 CrealityPrint: use spool holder print protocol when Ext selected
When the spool holder is selected for printing, use the opGcodeFile
command with enableSelfTest instead of colorMatch + multiColorPrint.
This matches the protocol used by CrealityPrint desktop for spool
holder prints.

- Detect spool holder mode: any colorMatch entry with box_id 0
- Spool holder: send opGcodeFile + enableSelfTest (single command)
- CFS: send colorMatch + multiColorPrint (existing behavior)

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
1608af485f CrealityPrint: add spool holder support to filament mapping
Show the spool holder (external filament roll) as a selectable option
in the K2 Plus filament mapping dialog. Previously only CFS slots were
shown because the box state filter excluded the spool holder.

- Only skip inactive CFS boxes (type 0, state != 1); spool holder
  (type 1) is always available
- Label spool holder slots as "Ext - {type}" in the dropdown
- Auto-match priority is now:
  1. CFS exact (type + color)
  2. CFS type-only
  3. Ext exact (type + color)
  4. Ext type-only
  5. Positional default
- When Ext is selected (by user or auto-match), disable other combos
  since firmware does not support mixing CFS and spool holder

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
0f55fbbb68 CrealityPrint: auto-match gcode filaments to CFS slots by color and type
When the filament mapping dialog opens, automatically select the best
matching CFS slot for each gcode filament by comparing color and type.
Falls back to positional index if no exact match is found.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
838e544d07 CrealityPrint: add filament mapping UI for K2 Plus
Query CFS material slots and show colored dropdowns to map gcode
filaments to physical printer slots. Color mapping is passed through
extended_info as colorMatch entries for the multi-color print protocol.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
---
v4:
- Use gcode tool index (T1A, T1B, ...) as colorMatch id instead of
  CFS slot tool_id — firmware expects the gcode filament identifier,
  not the destination slot
2026-05-18 09:41:32 +02:00
Igor Mammedov
35117ceedf CrealityPrint: add calibration checkbox to send dialog
Add enableSelfTest checkbox to CrealityPrintHostSendDialog that
persists across sessions via AppConfig. The checkbox state is
passed to the upload via extendedInfo().

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
02c71fa6c0 CrealityPrint: add printer model name and detection UI
Add model_name() to map firmware model codes (F008, F012, F021)
to human-readable names (K2 Plus, K2 Pro, K2). Update the send
dialog init() to detect multi-color support and show a group box
with the printer name.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
7dd2e5c6a5 CrealityPrint: add custom send dialog boilerplate
Add CrealityPrintHostSendDialog scaffolding: an empty dialog class
that inherits from PrintHostSendDialog, and Plater wiring to use
it when the host type is CrealityPrint.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
20b00b25c6 CrealityPrint: add multi-color print protocol for K2 Plus
For supported models, start_print() sends colorMatch (filament-to-slot
mapping) followed by multiColorPrint (with optional calibration via
enableSelfTest) instead of the legacy opGcodeFile command. The dialog
passes color mapping and calibration settings through extended_info.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
---
v4:
- Skip "path" form field in multipart upload for multi-color printers
  (extra field breaks K2 Plus firmware parser)
- Use fire-and-forget ws.write() for colorMatch and multiColorPrint
  instead of ws_send_and_read() (printer sends response asynchronously)
2026-05-18 09:41:32 +02:00
Igor Mammedov
767daa1201 CrealityPrint: add CFS material box query
Add query_boxes_info() to discover loaded materials in the CFS
(Creality Filament System) via websocket. Returns the boxsInfo
JSON with slot details (type, color, vendor, temperature range).

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
c746c48649 CrealityPrint: add ws_send_and_read() helper
Add a reusable websocket helper that sends a JSON command and loops
reads until finding a response containing the expected key. This
handles the printer's unsolicited status messages that arrive on
connect before the actual response. Returns empty string on timeout
instead of throwing.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
8e1ef9bc5f CrealityPrint: add ws_connect() helper
Consolidate websocket connection setup into a reusable helper.
Migrate from tcp::socket to beast::tcp_stream for timeout support.
Set SO_RCVTIMEO read timeout (3s) and connect timeout (5s).
Refactor query_boxes_info() and start_print() to use ws_connect().

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
aa9d472d9e CrealityPrint: add model detection from /info endpoint
Parse the model field from the /info JSON response to enable
model-specific features. Add supports_multi_color_print() which
returns true for K2-platform printers (K2 Plus, K2 Pro, K2).

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:32 +02:00
Igor Mammedov
e995541c25 CrealityPrint: propagate start_print() errors to caller
Change start_print() from void to bool with a wxString& msg
out-parameter, matching the error handling pattern used by Duet,
MKS, ESP3D and Flashforge. On failure, the error message is now
reported to the user via error_fn instead of being silently
swallowed. Use BOOST_LOG_TRIVIAL for logging instead of std::cerr.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:31 +02:00
Igor Mammedov
376841e28e Http: consolidate get_host_from_url() into Http class
Move duplicate get_host_from_url() implementations from ElegooLink and
OctoPrint into Http as a static method using the curl_url API. This
eliminates code duplication and provides a single reliable URL host
extraction utility for all print host implementations.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:31 +02:00
Igor Mammedov
8a650ec672 ElegooLink: fix host extraction for IP address parsing
get_host_from_url() returns host:port which may cause
boost::asio::ip::make_address() to fail when a port is present,
bypassing the direct IP upload path and falling through to DNS
resolution via upload_inner_with_host(). Use get_host_from_url_no_port()
to extract just the host.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
2026-05-18 09:41:31 +02:00
8527 changed files with 367434 additions and 202772 deletions

View File

@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup Bun
uses: oven-sh/setup-bun@v2

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup Bun
uses: oven-sh/setup-bun@v2

View File

@@ -5,6 +5,7 @@ on:
branches:
- main
- release/*
- belt-printer
paths:
- 'deps/**'
- 'src/**'
@@ -14,6 +15,8 @@ on:
- 'resources/**'
- ".github/workflows/build_*.yml"
- 'scripts/flatpak/**'
- 'scripts/msix/**'
- 'tests/**'
pull_request:
branches:
@@ -27,9 +30,12 @@ on:
- 'version.inc'
- ".github/workflows/build_*.yml"
- 'build_linux.sh'
- 'build_release_vs.bat'
- 'build_release_vs2022.bat'
- 'build_release_macos.sh'
- 'scripts/flatpak/**'
- 'scripts/msix/**'
- 'tests/**'
schedule:
@@ -51,19 +57,42 @@ jobs:
build_linux:
strategy:
fail-fast: false
# Build both arches on every event (PRs included), through the same
# build_check_cache -> build_deps -> build_orca chain (the AppImage).
# aarch64 always uses the GitHub-hosted arm runner (there is no arm
# self-hosted server). amd64's empty arch is load-bearing: it keeps the
# historical 'linux-clang' deps cache key and the unsuffixed asset names.
matrix:
include:
- arch: ""
os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
- arch: "aarch64"
os: ubuntu-24.04-arm
# Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
uses: ./.github/workflows/build_check_cache.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
os: ${{ matrix.os }}
arch: ${{ matrix.arch }}
build-deps-only: ${{ inputs.build-deps-only || false }}
secrets: inherit
build_windows:
name: Build Windows ${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
include:
- arch: x64
os: windows-latest
- arch: arm64
os: windows-11-arm
# Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
uses: ./.github/workflows/build_check_cache.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-win-server' || 'windows-latest' }}
# Self-hosted runner is x64-only; ARM64 always uses the GitHub-hosted runner.
os: ${{ (matrix.arch == 'x64' && vars.SELF_HOSTED) && 'orca-win-server' || matrix.os }}
arch: ${{ matrix.arch }}
build-deps-only: ${{ inputs.build-deps-only || false }}
force-build: ${{ github.event_name == 'schedule' }}
secrets: inherit
@@ -95,12 +124,14 @@ jobs:
secrets: inherit
unit_tests:
name: Unit Tests
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
# Tests are built on the aarch64 leg by default (faster GitHub arm runner),
# so run them there; self-hosted builds them on the amd64 server instead.
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04-arm' }}
needs: build_linux
if: ${{ !cancelled() && success() }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
sparse-checkout: |
.github
@@ -166,11 +197,14 @@ jobs:
date:
ver:
ver_pure:
# Belt-printer nightlies share the main nightly release but carry a `_belt`
# suffix so they never overwrite the main assets.
nightly_suffix: ${{ github.ref == 'refs/heads/belt-printer' && '_belt' || '' }}
steps:
- name: "Remove unneeded stuff to free disk space"
run:
rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/*
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Get the version and date
run: |
ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
@@ -189,14 +223,14 @@ jobs:
# Manage flatpak-builder cache externally so PRs restore but never upload
- name: Restore flatpak-builder cache
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: .flatpak-builder
key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.event.pull_request.base.sha }}
restore-keys: flatpak-builder-${{ matrix.variant.arch }}-
- name: Save/restore flatpak-builder cache
if: github.event_name != 'pull_request'
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: .flatpak-builder
key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.sha }}
@@ -224,13 +258,12 @@ jobs:
name: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
- name: Deploy Flatpak to nightly release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer')
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
asset_name: OrcaSlicer-Linux-flatpak_nightly_${{ matrix.variant.arch }}.flatpak
asset_name: OrcaSlicer-Linux-flatpak_nightly${{ env.nightly_suffix }}_${{ matrix.variant.arch }}.flatpak
asset_content_type: application/octet-stream
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted

View File

@@ -26,16 +26,19 @@ jobs:
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
lfs: 'false'
- name: set outputs
id: set_outputs
env:
# Keep macOS cache keys and paths architecture-specific.
cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && 'windows' || 'linux-clang') }}
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || '/OrcaSlicer_dep' }}
# Keep macOS/Windows cache keys architecture-specific. amd64 Linux passes
# no arch (key stays 'linux-clang', preserving the existing cache);
# aarch64 gets its own 'linux-clang-aarch64' key.
cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && format('windows-{0}', inputs.arch) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
# ARM64 builds use the build-arm64 tree (see build_release_vs.bat); x64/other use build.
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
run: |
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
@@ -43,7 +46,7 @@ jobs:
- name: load cache
id: cache_deps
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ${{ steps.set_outputs.outputs.cache-path }}
key: ${{ steps.set_outputs.outputs.cache-key }}

View File

@@ -34,22 +34,39 @@ jobs:
# Setup the environment
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
lfs: 'false'
- name: load cached deps
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}
- uses: lukka/get-cmake@latest
# The windows-11-arm runner needs CMake <= 3.31 (handled in the next step).
if: ${{ !(runner.os == 'Windows' && inputs.arch == 'arm64') }}
with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true
- name: Install CMake 3.31.x (Windows ARM64)
# windows-11-arm ships CMake 4.x, which removed pre-3.5 policy
# compatibility AND has incomplete ASM_ARMASM linker modules
# (breaks Boost.Context on ARM64). Pin to the last 3.x release.
if: runner.os == 'Windows' && inputs.arch == 'arm64'
shell: pwsh
run: |
$ver = "3.31.6"
$url = "https://github.com/Kitware/CMake/releases/download/v$ver/cmake-$ver-windows-arm64.zip"
Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\cmake.zip"
Expand-Archive -Path "$env:RUNNER_TEMP\cmake.zip" -DestinationPath "$env:RUNNER_TEMP\cmake" -Force
$cmakeBin = "$env:RUNNER_TEMP\cmake\cmake-$ver-windows-arm64\bin"
if (-not (Test-Path "$cmakeBin\cmake.exe")) { throw "cmake.exe not found at $cmakeBin" }
Add-Content -Path $env:GITHUB_PATH -Value $cmakeBin
- name: setup dev on Windows
if: runner.os == 'Windows'
uses: microsoft/setup-msbuild@v3
@@ -65,6 +82,50 @@ jobs:
shell: pwsh
- name: Install MSYS2 (clangarm64) with GMP/MPFR and LLVM tools
if: runner.os == 'Windows' && inputs.arch == 'arm64'
uses: msys2/setup-msys2@v2
with:
msystem: CLANGARM64
update: true
install: >-
mingw-w64-clang-aarch64-gmp
mingw-w64-clang-aarch64-mpfr
mingw-w64-clang-aarch64-llvm
- name: Stage ARM64 GMP/MPFR (no prebuilt blobs exist for win-arm64)
# GMP/MPFR ship prebuilt x64/x86 blobs in-tree but none for ARM64.
# Pull them from MSYS2 clangarm64 and generate MSVC import libs via
# llvm-dlltool, then stage into deps/{GMP,MPFR}/.../win-arm64 where the
# MSVC branch of GMP.cmake/MPFR.cmake copies them into the dep prefix.
if: runner.os == 'Windows' && inputs.arch == 'arm64'
shell: msys2 {0}
run: |
set -euo pipefail
BIN=/clangarm64/bin
REPO=$(cygpath -u "$GITHUB_WORKSPACE")
make_import_lib() {
local dll="$1"; local lib="$2"; local def="/tmp/${dll%.dll}.def"
echo "EXPORTS" > "$def"
llvm-readobj --coff-exports "$BIN/$dll" | awk '/Name: /{print $2}' >> "$def"
llvm-dlltool -m arm64 -D "$dll" -d "$def" -l "$BIN/$lib"
}
make_import_lib libgmp-10.dll libgmp-10.lib
# MPFR 4.x ships as libmpfr-6.dll; rename to libmpfr-4 BEFORE generating
# the import lib so the baked-in runtime DLL name is correct.
MPFR_DLL=$(ls $BIN/libmpfr-*.dll | head -1 | xargs basename)
if [ "$MPFR_DLL" != "libmpfr-4.dll" ]; then cp "$BIN/$MPFR_DLL" "$BIN/libmpfr-4.dll"; fi
make_import_lib libmpfr-4.dll libmpfr-4.lib
mkdir -p $REPO/deps/GMP/gmp/lib/win-arm64 $REPO/deps/MPFR/mpfr/lib/win-arm64
cp $BIN/libgmp-10.dll $BIN/libgmp-10.lib $REPO/deps/GMP/gmp/lib/win-arm64/
cp $BIN/libmpfr-4.dll $BIN/libmpfr-4.lib $REPO/deps/MPFR/mpfr/lib/win-arm64/
cp /clangarm64/include/gmp.h $REPO/deps/GMP/gmp/include/
cp /clangarm64/include/mpfr.h $REPO/deps/MPFR/mpfr/include/ || true
# Build Dependencies
- name: Build on Windows
if: runner.os == 'Windows'
@@ -73,8 +134,14 @@ jobs:
if (-not "${{ vars.SELF_HOSTED }}") {
choco install strawberryperl
}
.\build_release_vs.bat deps
.\build_release_vs.bat pack
$arch = "${{ inputs.arch }}"
if ($arch -eq "arm64") {
.\build_release_vs.bat deps arm64
.\build_release_vs.bat pack arm64
} else {
.\build_release_vs.bat deps
.\build_release_vs.bat pack
}
shell: pwsh
- name: Build on Mac ${{ inputs.arch }}

View File

@@ -29,27 +29,49 @@ jobs:
ubuntu-ver: '2404'
ubuntu-ver-str: '_Ubuntu2404'
ORCA_UPDATER_SIG_KEY: ${{ secrets.ORCA_UPDATER_SIG_KEY }}
# Branches whose builds are published to the nightly release. The
# belt-printer branch ships alongside main but its assets carry a `_belt`
# suffix (nightly_suffix) so they never overwrite the main nightly assets.
deploy_nightly: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer' }}
nightly_suffix: ${{ github.ref == 'refs/heads/belt-printer' && '_belt' || '' }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
lfs: 'false'
- name: load cached deps
if: ${{ !(runner.os == 'macOS' && inputs.macos-combine-only) }}
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}
fail-on-cache-miss: true
- uses: lukka/get-cmake@latest
# The windows-11-arm runner needs CMake <= 3.31 (handled in the next step).
if: ${{ !(runner.os == 'Windows' && inputs.arch == 'arm64') }}
with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true
- name: Install CMake 3.31.x (Windows ARM64)
# windows-11-arm ships CMake 4.x, which removed pre-3.5 policy
# compatibility AND has incomplete ASM_ARMASM linker modules
# (breaks Boost.Context on ARM64). Pin to the last 3.x release.
if: runner.os == 'Windows' && inputs.arch == 'arm64'
shell: pwsh
run: |
$ver = "3.31.6"
$url = "https://github.com/Kitware/CMake/releases/download/v$ver/cmake-$ver-windows-arm64.zip"
Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\cmake.zip"
Expand-Archive -Path "$env:RUNNER_TEMP\cmake.zip" -DestinationPath "$env:RUNNER_TEMP\cmake" -Force
$cmakeBin = "$env:RUNNER_TEMP\cmake\cmake-$ver-windows-arm64\bin"
if (-not (Test-Path "$cmakeBin\cmake.exe")) { throw "cmake.exe not found at $cmakeBin" }
Add-Content -Path $env:GITHUB_PATH -Value $cmakeBin
- name: Get the version and date on Ubuntu and macOS
if: runner.os != 'Windows'
run: |
@@ -65,6 +87,11 @@ jobs:
echo "ver_pure=$ver_pure" >> $GITHUB_ENV
echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
echo "git_commit_hash=$git_commit_hash" >> $GITHUB_ENV
# Per-arch Linux AppImage naming: amd64 keeps the historical unsuffixed
# name (arch_suffix empty). Unused on macOS/Windows.
if [ '${{ inputs.arch }}' = 'aarch64' ]; then
echo "arch_suffix=_aarch64" >> $GITHUB_ENV
fi
shell: bash
- name: Get the version and date on Windows
@@ -153,17 +180,9 @@ jobs:
run: |
./build_release_macos.sh -u -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a universal -t 10.15
- name: Delete intermediate per-arch artifacts
if: runner.os == 'macOS' && inputs.macos-combine-only
uses: geekyeggo/delete-artifact@v6
with:
name: |
OrcaSlicer_Mac_bundle_arm64_${{ github.sha }}
OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }}
# Thanks to RaySajuuk, it's working now
- name: Sign app and notary
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'macOS' && inputs.macos-combine-only
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'macOS' && inputs.macos-combine-only
working-directory: ${{ github.workspace }}
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
@@ -171,6 +190,8 @@ jobs:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
CERTIFICATE_ID: ${{ secrets.MACOS_CERTIFICATE_ID }}
run: |
# Load the `retry` helper (retries flaky commands such as `hdiutil create`).
source ${{ github.workspace }}/scripts/retry.sh
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
@@ -183,9 +204,14 @@ jobs:
security list-keychain -d user -s $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $P12_PASSWORD $KEYCHAIN_PATH
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer.app
# --deep is deprecated and signing/sealing can fail or skip files in
# non-standard layouts (python/bin, python/lib/**, tools/uv). Verify
# here so coverage gaps fail at CI time rather than at notarization.
codesign --verify --deep --strict --verbose=2 ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer.app
# Sign OrcaSlicer_profile_validator.app if it exists
if [ -f "${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app
codesign --verify --deep --strict --verbose=2 ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app
fi
# Create main OrcaSlicer DMG without the profile validator helper
@@ -193,7 +219,7 @@ jobs:
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/*
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer.app ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/Applications
hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
retry hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
# Create separate OrcaSlicer_profile_validator DMG if the app exists
@@ -202,7 +228,7 @@ jobs:
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/*
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/Applications
hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
retry hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
fi
@@ -217,14 +243,16 @@ jobs:
fi
- name: Create DMG without notary
if: github.ref != 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only
if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/belt-printer' && runner.os == 'macOS' && inputs.macos-combine-only
working-directory: ${{ github.workspace }}
run: |
# Load the `retry` helper (retries flaky commands such as `hdiutil create`).
source ${{ github.workspace }}/scripts/retry.sh
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/*
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer.app ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/Applications
hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
retry hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
# Create separate OrcaSlicer_profile_validator DMG if the app exists
if [ -f "${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
@@ -232,9 +260,19 @@ jobs:
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/*
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/Applications
hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
retry hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
fi
# Delete the per-arch bundles only after signing/DMG creation succeeded, so a
# failed run keeps them available for a re-run instead of forcing a full rebuild.
- name: Delete intermediate per-arch artifacts
if: success() && runner.os == 'macOS' && inputs.macos-combine-only
uses: geekyeggo/delete-artifact@v6
with:
name: |
OrcaSlicer_Mac_bundle_arm64_${{ github.sha }}
OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }}
- name: Upload artifacts mac
if: runner.os == 'macOS' && inputs.macos-combine-only
uses: actions/upload-artifact@v7
@@ -251,13 +289,13 @@ jobs:
if-no-files-found: ignore
- name: Deploy Mac release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
if: github.repository == 'OrcaSlicer/OrcaSlicer' && env.deploy_nightly == 'true' && runner.os == 'macOS' && inputs.macos-combine-only && !vars.SELF_HOSTED
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
asset_name: OrcaSlicer_Mac_universal_nightly.dmg
asset_name: OrcaSlicer_Mac_universal_nightly${{ env.nightly_suffix }}.dmg
asset_content_type: application/octet-stream
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
@@ -273,6 +311,18 @@ jobs:
max_releases: 1
# Windows
- name: Set Windows build variables
if: runner.os == 'Windows'
shell: pwsh
run: |
if ("${{ inputs.arch }}" -eq "arm64") {
"BUILD_DIR=build-arm64" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
"ARCH_SUFFIX=_arm64" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
} else {
"BUILD_DIR=build" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
"ARCH_SUFFIX=_x64" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
}
- name: setup MSVC
if: runner.os == 'Windows'
uses: microsoft/setup-msbuild@v3
@@ -290,23 +340,28 @@ jobs:
# env:
# WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\'
# WindowsSDKVersion: '10.0.26100.0\'
run: .\build_release_vs.bat slicer
run: |
$arch = "${{ inputs.arch }}"
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 } else { .\build_release_vs.bat slicer }
shell: pwsh
# NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's
# x86 emulation, packaging the native arm64 payload from build-arm64.
- name: Create installer Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}/build
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
run: |
cpack -G NSIS
- name: Pack app
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}/build
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
shell: cmd
run: '"C:/Program Files/7-Zip/7z.exe" a -tzip OrcaSlicer_Windows_${{ env.ver }}_portable.zip ${{ github.workspace }}/build/OrcaSlicer'
run: '"C:/Program Files/7-Zip/7z.exe" a -tzip OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable.zip ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer'
- name: Pack PDB
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}/build/src/Release
if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/src/Release
shell: cmd
run: '"C:/Program Files/7-Zip/7z.exe" a -m0=lzma2 -mx9 Debug_PDB_${{ env.ver }}_for_developers_only.7z *.pdb'
@@ -314,54 +369,54 @@ jobs:
if: runner.os == 'Windows'
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_Windows_${{ env.ver }}_portable
path: ${{ github.workspace }}/build/OrcaSlicer
name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer
- name: Upload artifacts Win installer
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_Windows_${{ env.ver }}
path: ${{ github.workspace }}/build/OrcaSlicer*.exe
name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer*.exe
- name: Upload artifacts Win PDB
if: runner.os == 'Windows' && !vars.SELF_HOSTED
if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v7
with:
name: PDB
path: ${{ github.workspace }}/build/src/Release/Debug_PDB_${{ env.ver }}_for_developers_only.7z
- name: Upload OrcaSlicer_profile_validator Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_profile_validator_Windows_${{ env.ver }}
path: ${{ github.workspace }}/build/src/Release/OrcaSlicer_profile_validator.exe
- name: Deploy Windows release portable
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
if: github.repository == 'OrcaSlicer/OrcaSlicer' && env.deploy_nightly == 'true' && runner.os == 'Windows' && !vars.SELF_HOSTED
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/build/OrcaSlicer_Windows_${{ env.ver }}_portable.zip
asset_name: OrcaSlicer_Windows_nightly_portable.zip
asset_path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable.zip
asset_name: OrcaSlicer_Windows${{ env.ARCH_SUFFIX }}_nightly${{ env.nightly_suffix }}_portable.zip
asset_content_type: application/x-zip-compressed
max_releases: 1
- name: Deploy Windows release installer
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
if: github.repository == 'OrcaSlicer/OrcaSlicer' && env.deploy_nightly == 'true' && runner.os == 'Windows' && !vars.SELF_HOSTED
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ${{ github.workspace }}/build/OrcaSlicer_Windows_Installer_${{ env.ver }}.exe
asset_name: OrcaSlicer_Windows_Installer_nightly.exe
asset_path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe
asset_name: OrcaSlicer_Windows_Installer${{ env.ARCH_SUFFIX }}_nightly${{ env.nightly_suffix }}.exe
asset_content_type: application/x-msdownload
max_releases: 1
- name: Deploy Windows OrcaSlicer_profile_validator release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && !vars.SELF_HOSTED
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
@@ -371,6 +426,26 @@ jobs:
asset_content_type: application/x-msdownload
max_releases: 1
- name: Build MSIX Store package Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}
shell: pwsh
run: |
./scripts/msix/build_msix.ps1 `
-InstallDir "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer" `
-OutputPath "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_MSIX_${{ env.ver }}${{ env.ARCH_SUFFIX }}.msix" `
-Architecture "${{ inputs.arch }}" `
-IdentityName "${{ vars.ORCA_MSIX_IDENTITY_NAME || 'OrcaSlicer.OrcaSlicer' }}" `
-Publisher "${{ vars.ORCA_MSIX_PUBLISHER || 'CN=38F7EA55-C73B-4072-B3B2-C8E0EA15BB82' }}" `
-PublisherDisplayName "${{ vars.ORCA_MSIX_PUBLISHER_DISPLAY_NAME || 'OrcaSlicer' }}"
- name: Upload artifacts Win MSIX
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_Windows_MSIX_${{ env.ver }}${{ env.ARCH_SUFFIX }}
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_MSIX_${{ env.ver }}${{ env.ARCH_SUFFIX }}.msix
# Ubuntu
- name: Apt-Install Dependencies
if: runner.os == 'Linux' && !vars.SELF_HOSTED
@@ -382,16 +457,23 @@ jobs:
if: runner.os == 'Linux'
shell: bash
run: |
./build_linux.sh -istrlL
# Build + tar the unit tests (-t) only on the leg that runs them: the
# aarch64 leg by default (faster GitHub arm runner), or amd64 when using
# self-hosted runners (no arm self-hosted server). unit_tests downloads
# this tarball. The profile validator is built with -s, so amd64 keeps it.
tests=${{ (!vars.SELF_HOSTED && inputs.arch == 'aarch64') || (vars.SELF_HOSTED && inputs.arch != 'aarch64') }}
if $tests; then flags=-istrlL; else flags=-isrlL; fi
./build_linux.sh "$flags"
./scripts/check_appimage_libs.sh ./build/package ./build/package/bin/orca-slicer
mv -n ./build/OrcaSlicer_Linux_V${{ env.ver_pure }}.AppImage ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
chmod +x ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
tar -cvpf build_tests.tar build/tests
appimage=./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_${{ env.ver }}.AppImage
mv -n ./build/OrcaSlicer_Linux_V${{ env.ver_pure }}.AppImage "$appimage"
chmod +x "$appimage"
if $tests; then tar -cvpf build_tests.tar build/tests; fi
# Use tar because upload-artifacts won't always preserve directory structure
# and doesn't preserve file permissions
- name: Upload Test Artifact
if: runner.os == 'Linux'
if: runner.os == 'Linux' && ((!vars.SELF_HOSTED && inputs.arch == 'aarch64') || (vars.SELF_HOSTED && inputs.arch != 'aarch64'))
uses: actions/upload-artifact@v7
with:
name: ${{ github.sha }}-tests
@@ -401,7 +483,7 @@ jobs:
if-no-files-found: error
- name: Run external slicer regression tests
if: runner.os == 'Linux'
if: runner.os == 'Linux' && inputs.arch != 'aarch64'
timeout-minutes: 20
shell: bash
run: |
@@ -411,7 +493,7 @@ jobs:
python3 "$test_repo_dir/run_test.py" "${{ github.workspace }}/build/package/bin/orca-slicer"
- name: Build orca_custom_preset_tests
if: github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED
if: github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64'
working-directory: ${{ github.workspace }}/build/src/Release
shell: bash
run: |
@@ -423,28 +505,28 @@ jobs:
if: ${{ ! env.ACT && runner.os == 'Linux' }}
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_Linux_ubuntu_${{ env.ubuntu-ver }}_${{ env.ver }}
path: './build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage'
name: OrcaSlicer_Linux_ubuntu_${{ env.ubuntu-ver }}${{ env.arch_suffix }}_${{ env.ver }}
path: "./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_${{ env.ver }}.AppImage"
- name: Upload OrcaSlicer_profile_validator Ubuntu
if: ${{ ! env.ACT && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ ! env.ACT && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_profile_validator_Linux_ubuntu_${{ env.ubuntu-ver }}_${{ env.ver }}
path: './build/src/Release/OrcaSlicer_profile_validator'
- name: Deploy Ubuntu release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && env.deploy_nightly == 'true' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
release_id: 137995723
asset_path: ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
asset_name: OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_nightly.AppImage
asset_path: ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_${{ env.ver }}.AppImage
asset_name: OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_nightly${{ env.nightly_suffix }}.AppImage
asset_content_type: application/octet-stream
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Deploy Ubuntu release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
uses: rickstaa/action-create-tag@v1
with:
tag: "nightly-builds"
@@ -453,7 +535,7 @@ jobs:
message: "nightly-builds"
- name: Deploy Ubuntu OrcaSlicer_profile_validator release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
@@ -464,7 +546,7 @@ jobs:
max_releases: 1
- name: Deploy orca_custom_preset_tests
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install gettext
run: |

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Run extra JSON check
id: extra_json_check
@@ -38,7 +38,7 @@ jobs:
- name: Download
working-directory: ${{ github.workspace }}
run: |
curl -LJO https://github.com/SoftFever/Orca_tools/releases/download/1/OrcaSlicer_profile_validator
curl -L -o OrcaSlicer_profile_validator https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds/OrcaSlicer_profile_validator_Linux_Ubuntu2404_nightly
chmod +x ./OrcaSlicer_profile_validator
# validate profiles
@@ -49,17 +49,129 @@ jobs:
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log
exit ${PIPESTATUS[0]}
# For now run filament subtype check only for BBL profiles until we fix other vendors' profiles.
- name: validate filament subtype check for BBL profiles
id: validate_filament_subtypes
continue-on-error: true
run: |
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 -v BBL -f 2>&1 | tee ${{ runner.temp }}/validate_filament_subtypes.log
exit ${PIPESTATUS[0]}
# Flag inherits/compatible_printers/compatible_prints references that point at a deleted or
# renamed preset. Opt-in per vendor for now (via -r); enabled for BBL and Qidi until other
# vendors' profiles are cleaned up. Runs before the custom-preset injection below.
- name: validate preset references for BBL and Qidi profiles
id: validate_preset_references
continue-on-error: true
run: |
set +e
rc=0
for v in BBL Qidi; do
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 -v "$v" -r 2>&1 | tee -a ${{ runner.temp }}/validate_preset_references.log
[ ${PIPESTATUS[0]} -ne 0 ] && rc=1
done
exit $rc
- name: validate custom presets
id: validate_custom
continue-on-error: true
working-directory: ${{ github.workspace }}
run: |
set +e
curl -LJO https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds/orca_custom_preset_tests.zip
unzip -q ./orca_custom_preset_tests.zip -d ${{ github.workspace }}/resources/profiles
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_custom.log
exit ${PIPESTATUS[0]}
fixtures_dir="${{ runner.temp }}/profile-fixtures"
output_dir="${{ runner.temp }}/custom-preset-validation"
combined_log="${{ runner.temp }}/validate_custom.log"
summary="${output_dir}/summary.md"
release_url="https://github.com/OrcaSlicer/OrcaSlicer-profile-validator/releases/download/fixture-archive"
rm -rf "${fixtures_dir}" "${output_dir}"
mkdir -p "${fixtures_dir}" "${output_dir}"
curl -fsSL -o "${fixtures_dir}/manifest.json" "${release_url}/manifest.json"
MANIFEST_PATH="${fixtures_dir}/manifest.json" python3 <<'PY' > "${fixtures_dir}/fixtures.tsv"
import json
import os
with open(os.environ["MANIFEST_PATH"], encoding="utf-8") as fh:
manifest = json.load(fh)
if isinstance(manifest, dict):
entries = manifest.get("fixtures", [])
else:
entries = manifest
for entry in entries:
version = entry.get("version", "")
asset = entry.get("asset", "")
sha256 = entry.get("asset_sha256", "")
if not version or not asset:
continue
print(f"{version}\t{asset}\t{sha256}")
PY
if [ ! -s "${fixtures_dir}/fixtures.tsv" ]; then
echo "No custom preset fixtures found in ${release_url}/manifest.json" | tee "${combined_log}"
exit 1
fi
{
echo "## Custom Preset Fixture Validation"
echo ""
echo "| Version | Status | Log |"
echo "| --- | --- | --- |"
} > "${summary}"
status=0
failed_logs=()
while IFS=$'\t' read -r version asset expected_sha256; do
fixture_zip="${fixtures_dir}/${asset}"
asset_url_name="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${asset}")"
profile_tree="${output_dir}/profiles-${version}"
log_path="${output_dir}/${version}.log"
curl -fsSL -o "${fixture_zip}" "${release_url}/${asset_url_name}"
if [ -n "${expected_sha256}" ] && [ "${expected_sha256}" != "<sha256>" ]; then
echo "${expected_sha256} ${fixture_zip}" | sha256sum -c -
fi
rm -rf "${profile_tree}"
mkdir -p "${profile_tree}"
cp -a "${{ github.workspace }}/resources/profiles/." "${profile_tree}/"
rm -rf "${profile_tree}/user"
unzip -q "${fixture_zip}" -d "${profile_tree}"
set +e
./OrcaSlicer_profile_validator -p "${profile_tree}" -l 2 > "${log_path}" 2>&1
result=$?
set -e
if [ "${result}" -eq 0 ]; then
echo "| ${version} | PASS | ${version}.log |" >> "${summary}"
else
echo "| ${version} | FAIL | ${version}.log |" >> "${summary}"
failed_logs+=("${log_path}")
status=1
fi
done < "${fixtures_dir}/fixtures.tsv"
{
cat "${summary}"
if [ "${#failed_logs[@]}" -gt 0 ]; then
echo ""
echo "## Failed Fixture Logs"
for log_path in "${failed_logs[@]}"; do
echo ""
echo "### $(basename "${log_path}" .log)"
echo '```'
head -c 12000 "${log_path}" || echo "No output captured"
echo '```'
done
fi
} | tee "${combined_log}"
exit "${status}"
- name: Prepare PR number for comment workflow
if: ${{ always() && github.event_name == 'pull_request' }}
@@ -68,7 +180,7 @@ jobs:
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_preset_references.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
{
# Marker matched by check_profiles_comment.yml to delete prior comments.
@@ -94,6 +206,24 @@ jobs:
echo ""
fi
if [ "${{ steps.validate_filament_subtypes.outcome }}" = "failure" ]; then
echo "### BBL Filament Subtype Validation Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/validate_filament_subtypes.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_preset_references.outcome }}" = "failure" ]; then
echo "### BBL/Qidi Preset Reference Validation Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/validate_preset_references.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_custom.outcome }}" = "failure" ]; then
echo "### Custom Preset Validation Failed"
echo ""
@@ -116,7 +246,7 @@ jobs:
retention-days: 1
- name: Fail if any check failed
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_preset_references.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
echo "One or more profile checks failed. See above for details."
exit 1

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Run Claude Code slash command
uses: anthropics/claude-code-base-action@beta

View File

@@ -26,7 +26,7 @@ jobs:
remove-existing-swap-files: true
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install Doxygen and Graphviz
run: |

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Ask PR author for label
uses: actions/github-script@v7
uses: actions/github-script@v9
with:
script: |
function isPermissionDenied(error) {
@@ -88,7 +88,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Apply label command from PR author
uses: actions/github-script@v7
uses: actions/github-script@v9
with:
script: |
function isPermissionDenied(error) {

131
.github/workflows/publish_release.yml vendored Normal file
View File

@@ -0,0 +1,131 @@
name: Publish to draft release
# Manually pulls the platform binaries produced by a "Build all" run and uploads
# them to an existing DRAFT release. Decoupled from the build so you can test a
# build first, then publish exactly that run's artifacts once you're happy.
#
# Trigger: Actions tab -> "Publish to draft release" -> Run workflow.
# Locked to a single person: the first step aborts unless the actor matches the
# `RELEASE_PUBLISHER` repo variable (set to "SoftFever"). To allow someone else,
# change that variable: gh variable set RELEASE_PUBLISHER --body "<login>".
on:
workflow_dispatch:
inputs:
run_id:
description: 'Run ID of the "Build all" workflow to pull binaries from (the number in the Actions run URL)'
required: true
type: string
tag:
description: 'Tag of the draft release to upload to (e.g. v2.4.0-beta)'
required: true
type: string
permissions:
contents: write # upload release assets
actions: read # download artifacts from another run
jobs:
publish:
name: Publish binaries to draft release
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_ID: ${{ inputs.run_id }}
TAG: ${{ inputs.tag }}
steps:
- name: Restrict to the release publisher
env:
PUBLISHER: ${{ vars.RELEASE_PUBLISHER }}
run: |
if [ -z "$PUBLISHER" ]; then
echo "::error::RELEASE_PUBLISHER repo variable is not set."
exit 1
fi
if [ "${{ github.actor }}" != "$PUBLISHER" ]; then
echo "::error::Only @$PUBLISHER may run this workflow (you are @${{ github.actor }})."
exit 1
fi
echo "Authorized: @${{ github.actor }}"
- name: Verify target is a draft release
run: |
if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft >/dev/null 2>&1; then
echo "::error::Release '$TAG' not found. Create the draft (with this tag) first."
exit 1
fi
is_draft=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft')
if [ "$is_draft" != "true" ]; then
echo "::error::Release '$TAG' is published, not a draft. Aborting to avoid touching a live release."
exit 1
fi
- name: Verify source build run
run: |
info=$(gh run view "$RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,conclusion,headBranch)
name=$(echo "$info" | jq -r '.workflowName')
conclusion=$(echo "$info" | jq -r '.conclusion')
branch=$(echo "$info" | jq -r '.headBranch')
echo "Source run #$RUN_ID: '$name' on '$branch' (conclusion: $conclusion)"
if [ "$conclusion" != "success" ]; then
echo "::warning::Source run did not conclude 'success' ($conclusion). Some assets may be missing."
fi
- name: Download release artifacts from build run
run: |
# Windows_V* (not Windows_*) keeps the MSIX Store artifact out: it goes to Partner Center, not GitHub releases.
gh run download "$RUN_ID" --repo "$GITHUB_REPOSITORY" --dir artifacts \
-p 'OrcaSlicer_Windows_V*' \
-p 'OrcaSlicer_Mac_universal_*' \
-p 'OrcaSlicer_Linux_ubuntu_*' \
-p 'OrcaSlicer-Linux-flatpak_*' \
-p 'PDB'
echo "Downloaded artifact folders:"
ls -1 artifacts
- name: Assemble release assets
run: |
set -euo pipefail
mkdir -p upload
# gh run download auto-extracts each artifact into a folder, so the inner
# binaries are already unzipped. Copy the inner binary for each platform.
# -type f is required (some artifact *folders* are named "*.flatpak").
# Windows installers (x64 + arm64): the .exe inside each installer
# artifact, NOT the orca-slicer.exe in the portable app folder. CPack
# now bakes the arch into the filename (…_x64.exe / …_arm64.exe), so
# copy them straight through.
find artifacts -type f -name '*.exe' -path '*OrcaSlicer_Windows_V*' ! -path '*_portable*' -exec cp -v {} upload/ \;
# macOS universal DMG (profile-validator DMG isn't downloaded).
find artifacts -type f -name '*.dmg' -path '*OrcaSlicer_Mac_universal_*' -exec cp -v {} upload/ \;
# Linux AppImage.
find artifacts -type f -name '*.AppImage' -exec cp -v {} upload/ \;
# Flatpak bundles (x86_64 + aarch64).
find artifacts -type f -name '*.flatpak' -exec cp -v {} upload/ \;
# Windows debug symbols (PDB archive, for developers).
find artifacts -type f -name 'Debug_PDB_*.7z' -exec cp -v {} upload/ \;
# Portable Windows builds (x64 + arm64) are unzipped folder artifacts;
# re-zip each to its released filename (these stay .zip on the release).
mapfile -t portable_dirs < <(find artifacts -maxdepth 1 -type d -name 'OrcaSlicer_Windows_*_portable')
if [ ${#portable_dirs[@]} -eq 0 ]; then
echo "::warning::Windows portable artifact not found."
fi
for portable_dir in "${portable_dirs[@]}"; do
( cd "$portable_dir" && zip -qr "$GITHUB_WORKSPACE/upload/$(basename "$portable_dir").zip" . )
echo "Zipped portable -> $(basename "$portable_dir").zip"
done
echo "Assets to upload:"
ls -lh upload
if [ -z "$(ls -A upload)" ]; then
echo "::error::No assets assembled. Check the run_id and that its artifacts haven't expired."
exit 1
fi
- name: Upload assets to draft release
run: |
gh release upload "$TAG" upload/* --repo "$GITHUB_REPOSITORY" --clobber
echo "Uploaded to draft release: $TAG"
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name'

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Cache shellcheck download
id: cache-shellcheck-v0_11
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/shellcheck
key: ${{ runner.os }}-shellcheck-v0_11
@@ -36,7 +36,7 @@ jobs:
tar -xvf ~/sc.tar.xz -C ~
mv ~/shellcheck-"${INPUT_VERSION}"/shellcheck ~/shellcheck
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 1

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup Python
uses: actions/setup-python@v6

5
.gitignore vendored
View File

@@ -45,4 +45,7 @@ test.js
.clangd
internal_docs/
*.flatpak
/flatpak-repo/
/flatpak-repo/
# Python bytecode
__pycache__/
*.pyc

View File

@@ -125,6 +125,131 @@ option(SLIC3R_PCH "Use precompiled headers" 1)
option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1)
option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0)
# Python stubgen module
option(ORCA_BUILD_PYTHON_STUBGEN_MODULE "Build importable Python module for pybind11-stubgen" ON)
set(ORCA_BUNDLED_UV_EXECUTABLE "" CACHE FILEPATH "Path to a uv executable to bundle for Python package installation. Leave empty to auto-download.")
# Auto-download uv if not provided by the user.
# uv is pinned: the executable ships inside the signed app, so the build must
# be reproducible and the download verified. To upgrade, bump ORCA_UV_VERSION
# and refresh every hash below from the release's .sha256 assets, e.g.:
# curl -sL https://github.com/astral-sh/uv/releases/download/<ver>/uv-<triple>.tar.gz.sha256
set(ORCA_UV_VERSION "0.11.21")
set(ORCA_UV_SHA256_aarch64-apple-darwin "1f921d491ba5ffeea774eb04d6681ecee379101341cbb1500394993b541bf3f4")
set(ORCA_UV_SHA256_x86_64-apple-darwin "f3c8e5708a84b920c18b691214d54d2b0da6b984789caae95d47c95120cb7765")
set(ORCA_UV_SHA256_aarch64-unknown-linux-gnu "88e800834007cc5efd4675f166eb2a51e7e3ad19876d85fa8805a6fb5c922397")
set(ORCA_UV_SHA256_x86_64-unknown-linux-gnu "8c88519b0ef0af9801fcdee419bbb12116bd9e6b18e162ae093c932d8b264050")
set(ORCA_UV_SHA256_x86_64-pc-windows-msvc "ace861f360c6de2babedc1607d0f454b6b09a820dbc8182dc15af927e4df9589")
# Version-scoped cache dir so a version bump invalidates the cached binary.
set(ORCA_UV_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/.uv/${ORCA_UV_VERSION}")
set(ORCA_UV_BINARY "${ORCA_UV_DOWNLOAD_DIR}/uv")
if(WIN32)
set(ORCA_UV_BINARY "${ORCA_UV_DOWNLOAD_DIR}/uv.exe")
endif()
# Older configures FORCE-cached the auto-downloaded path; any cache entry
# under .uv/ is that legacy value -- clear it so auto-download re-resolves
# (user-provided paths never live under .uv/).
if(ORCA_BUNDLED_UV_EXECUTABLE MATCHES "/\\.uv/")
set(ORCA_BUNDLED_UV_EXECUTABLE "" CACHE FILEPATH "Path to the auto-downloaded uv executable" FORCE)
endif()
if(NOT ORCA_BUNDLED_UV_EXECUTABLE)
if(NOT EXISTS "${ORCA_UV_BINARY}")
# Select uv by TARGET arch. On macOS universal builds each leg sets a single
# CMAKE_OSX_ARCHITECTURES (and the x86_64 leg cross-builds on an arm64 runner),
# so prefer it over the host CMAKE_SYSTEM_PROCESSOR -- otherwise both legs fetch
# the same arch and the later universal lipo merge of tools/uv/uv fails.
set(_orca_uv_proc "${CMAKE_SYSTEM_PROCESSOR}")
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
set(_orca_uv_proc "${CMAKE_OSX_ARCHITECTURES}")
endif()
# All release archives are tar.gz except the Windows zip.
set(ORCA_UV_EXT "tar.gz")
if(_orca_uv_proc MATCHES "x86_64|AMD64|amd64")
if(WIN32)
set(ORCA_UV_ARCH "x86_64-pc-windows-msvc")
set(ORCA_UV_EXT "zip")
elseif(APPLE)
set(ORCA_UV_ARCH "x86_64-apple-darwin")
else()
set(ORCA_UV_ARCH "x86_64-unknown-linux-gnu")
endif()
elseif(_orca_uv_proc MATCHES "aarch64|arm64|ARM64")
if(APPLE)
set(ORCA_UV_ARCH "aarch64-apple-darwin")
else()
set(ORCA_UV_ARCH "aarch64-unknown-linux-gnu")
endif()
else()
message(WARNING "Unsupported architecture for auto-downloading uv: ${_orca_uv_proc}")
endif()
if(ORCA_UV_ARCH)
set(ORCA_UV_URL "https://github.com/astral-sh/uv/releases/download/${ORCA_UV_VERSION}/uv-${ORCA_UV_ARCH}.${ORCA_UV_EXT}")
set(ORCA_UV_ARCHIVE "${ORCA_UV_DOWNLOAD_DIR}/uv-archive.${ORCA_UV_EXT}")
file(MAKE_DIRECTORY "${ORCA_UV_DOWNLOAD_DIR}")
message(STATUS "Downloading uv ${ORCA_UV_VERSION} from ${ORCA_UV_URL} ...")
file(DOWNLOAD "${ORCA_UV_URL}" "${ORCA_UV_ARCHIVE}" STATUS ORCA_UV_DL_STATUS TLS_VERIFY ON)
list(GET ORCA_UV_DL_STATUS 0 ORCA_UV_DL_CODE)
list(GET ORCA_UV_DL_STATUS 1 ORCA_UV_DL_MSG)
if(NOT ORCA_UV_DL_CODE EQUAL 0)
# Network failure keeps the historical graceful degradation (uv is
# optional at build time) -- but a hash mismatch below is fatal.
message(WARNING "Failed to download uv: ${ORCA_UV_DL_MSG}")
file(REMOVE "${ORCA_UV_ARCHIVE}")
else()
file(SHA256 "${ORCA_UV_ARCHIVE}" _orca_uv_actual_sha256)
if(NOT _orca_uv_actual_sha256 STREQUAL "${ORCA_UV_SHA256_${ORCA_UV_ARCH}}")
file(REMOVE "${ORCA_UV_ARCHIVE}")
message(FATAL_ERROR
"uv archive checksum mismatch for ${ORCA_UV_ARCH} ${ORCA_UV_VERSION}:\n"
" expected ${ORCA_UV_SHA256_${ORCA_UV_ARCH}}\n"
" actual ${_orca_uv_actual_sha256}\n"
"Possible tampering or a stale pin; refusing to bundle.")
endif()
message(STATUS "Extracting uv ...")
file(ARCHIVE_EXTRACT INPUT "${ORCA_UV_ARCHIVE}" DESTINATION "${ORCA_UV_DOWNLOAD_DIR}")
file(REMOVE "${ORCA_UV_ARCHIVE}")
# Pinned archives have a fixed layout: the tarballs hold
# uv-<triple>/uv, the Windows zip holds uv.exe at the root
# (already at ORCA_UV_BINARY). A layout change implies a new
# archive, hence a hash bump -- and RENAME fails loudly.
if(NOT WIN32)
file(RENAME "${ORCA_UV_DOWNLOAD_DIR}/uv-${ORCA_UV_ARCH}/uv" "${ORCA_UV_BINARY}")
file(REMOVE_RECURSE "${ORCA_UV_DOWNLOAD_DIR}/uv-${ORCA_UV_ARCH}")
endif()
endif()
endif()
endif()
if(EXISTS "${ORCA_UV_BINARY}")
# Plain set (shadows the empty cache entry for this configure run):
# the version-scoped path re-derives every configure, so a version
# bump takes effect in warm build dirs without cache surgery.
set(ORCA_BUNDLED_UV_EXECUTABLE "${ORCA_UV_BINARY}")
endif()
endif()
# Resolve ORCA_BUNDLED_UV_EXECUTABLE -> ORCA_BUNDLED_UV_EXECUTABLE_CONFIG / ORCA_BUNDLED_UV_FILENAME
# Must run before add_subdirectory(src) so target_compile_definitions sees it.
if(ORCA_BUNDLED_UV_EXECUTABLE)
if(EXISTS "${ORCA_BUNDLED_UV_EXECUTABLE}")
file(TO_CMAKE_PATH "${ORCA_BUNDLED_UV_EXECUTABLE}" ORCA_BUNDLED_UV_EXECUTABLE_CONFIG)
get_filename_component(ORCA_BUNDLED_UV_FILENAME "${ORCA_BUNDLED_UV_EXECUTABLE}" NAME)
else()
message(WARNING "ORCA_BUNDLED_UV_EXECUTABLE does not exist: ${ORCA_BUNDLED_UV_EXECUTABLE}")
set(ORCA_BUNDLED_UV_EXECUTABLE "")
set(ORCA_BUNDLED_UV_EXECUTABLE_CONFIG "")
set(ORCA_BUNDLED_UV_FILENAME "")
endif()
else()
set(ORCA_BUNDLED_UV_EXECUTABLE_CONFIG "")
set(ORCA_BUNDLED_UV_FILENAME "")
endif()
# If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, othrewise variable.
CMAKE_DEPENDENT_OPTION(SLIC3R_DESKTOP_INTEGRATION "Allow performing desktop integration during runtime" 1 "NOT SLIC3R_FHS" 0)
@@ -220,7 +345,9 @@ if (MSVC)
# /bigobj (Increase Number of Sections in .Obj file)
# error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater
# Generate symbols at every build target, even for the release.
add_compile_options(-bigobj -Zm520 /Zi)
# -Zm520 fixes error C3859 but forces the compiler to pre-allocate that memory for every translation unit regardless
# combining /Zi with /FS frees up a significant amount of memory pressure across all parallel compile jobs and makes /MP faster overall.
add_compile_options(-bigobj /Zi /FS)
# Disable STL4007: Many result_type typedefs and all argument_type, first_argument_type, and second_argument_type typedefs are deprecated in C++17.
#FIXME Remove this line after eigen library adapts to the new C++17 adaptor rules.
add_compile_options(-D_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING)
@@ -231,6 +358,11 @@ if (MSVC)
# Disable warnings on comparison of unsigned and signed
# C4018: signed/unsigned mismatch
add_compile_options(/wd4018)
# Prevent linker restart when TBB (or other deps) built with /GL are linked
# Make your full (clean) build slower because it enables link-time code generation across all translation units.
# helps incremental builds by preventing the double-link restart from TBB
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LTCG")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /LTCG")
endif ()
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang" AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER 15)
@@ -743,6 +875,86 @@ endforeach()
find_package(NLopt 1.4 REQUIRED)
# Use bundled Python from deps instead of system Python
set(_bundled_python_version "3.12.3")
set(_bundled_python_abi "312")
string(REGEX REPLACE "^([0-9]+\\.[0-9]+)\\..*$" "\\1" _bundled_python_version_short "${_bundled_python_version}")
set(_bundled_python_root "${CMAKE_PREFIX_PATH}/libpython")
set(Python3_ROOT_DIR "${_bundled_python_root}" CACHE PATH "Root directory for bundled Python" FORCE)
set(Python3_USE_STATIC_LIBS OFF)
set(Python3_FIND_STRATEGY LOCATION)
set(Python3_FIND_IMPLEMENTATIONS CPython)
if(WIN32)
set(_bundled_python_executable "${_bundled_python_root}/python.exe")
set(_bundled_python_library "${_bundled_python_root}/libs/python${_bundled_python_abi}.lib")
if(EXISTS "${_bundled_python_root}/python_d.exe")
set(_bundled_python_executable "${_bundled_python_root}/python_d.exe")
endif()
if(EXISTS "${_bundled_python_root}/libs/python${_bundled_python_abi}_d.lib")
set(_bundled_python_library "${_bundled_python_root}/libs/python${_bundled_python_abi}_d.lib")
endif()
set(Python3_FIND_REGISTRY NEVER)
set(Python3_EXECUTABLE "${_bundled_python_executable}" CACHE FILEPATH "Bundled Python executable" FORCE)
set(Python3_INCLUDE_DIR "${_bundled_python_root}/include" CACHE PATH "Bundled Python include directory" FORCE)
set(Python3_LIBRARY "${_bundled_python_library}" CACHE FILEPATH "Bundled Python embed import library" FORCE)
elseif(APPLE)
set(Python3_FIND_FRAMEWORK NEVER)
find_program(_bundled_python_executable
NAMES python${_bundled_python_version_short} python3
PATHS "${_bundled_python_root}/bin"
NO_DEFAULT_PATH
)
find_path(_bundled_python_include_dir
NAMES Python.h
PATHS
"${_bundled_python_root}/include/python${_bundled_python_version_short}"
"${_bundled_python_root}/include"
NO_DEFAULT_PATH
)
find_library(_bundled_python_library
NAMES python${_bundled_python_version_short} libpython${_bundled_python_version_short}
PATHS "${_bundled_python_root}/lib"
NO_DEFAULT_PATH
)
if(NOT _bundled_python_executable)
message(FATAL_ERROR "Bundled Python executable not found under ${_bundled_python_root}/bin")
endif()
if(NOT _bundled_python_include_dir)
message(FATAL_ERROR "Bundled Python headers not found under ${_bundled_python_root}/include")
endif()
if(NOT _bundled_python_library)
message(FATAL_ERROR "Bundled Python library not found under ${_bundled_python_root}/lib")
endif()
set(Python3_EXECUTABLE "${_bundled_python_executable}" CACHE FILEPATH "Bundled Python executable" FORCE)
set(Python3_INCLUDE_DIR "${_bundled_python_include_dir}" CACHE PATH "Bundled Python include directory" FORCE)
set(Python3_LIBRARY "${_bundled_python_library}" CACHE FILEPATH "Bundled Python embed library" FORCE)
endif()
find_package(Python3 ${_bundled_python_version} EXACT REQUIRED
COMPONENTS Interpreter Development.Embed
)
# Provide a minimal pybind11::embed target sourced from deps_src/pybind11 headers.
set(PYBIND11_SOURCE_DIR "${CMAKE_SOURCE_DIR}/deps_src/pybind11")
if(NOT EXISTS "${PYBIND11_SOURCE_DIR}/include/pybind11/pybind11.h")
message(FATAL_ERROR "pybind11 headers not found in ${PYBIND11_SOURCE_DIR}. Did you initialize submodules?")
endif()
add_library(pybind11_headers INTERFACE)
target_include_directories(pybind11_headers INTERFACE "${PYBIND11_SOURCE_DIR}/include")
add_library(pybind11::headers ALIAS pybind11_headers)
add_library(pybind11::pybind11 ALIAS pybind11_headers)
add_library(pybind11_embed INTERFACE)
target_link_libraries(pybind11_embed INTERFACE pybind11_headers Python3::Python)
target_compile_definitions(pybind11_embed INTERFACE PYBIND11_SIMPLE_GIL_MANAGEMENT)
add_library(pybind11::embed ALIAS pybind11_embed)
if(SLIC3R_STATIC)
set(OPENVDB_USE_STATIC_LIBS ON)
@@ -892,10 +1104,12 @@ if (NOT WIN32 AND NOT APPLE)
configure_file(${LIBDIR}/dev-utils/platform/unix/build_appimage.sh.in ${CMAKE_CURRENT_BINARY_DIR}/build_appimage.sh USE_SOURCE_PERMISSIONS @ONLY)
endif()
# Resources install target, configure fhs.hpp on UNIX
if (WIN32)
install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "./resources")
if(ORCA_BUNDLED_UV_EXECUTABLE)
install(PROGRAMS "${ORCA_BUNDLED_UV_EXECUTABLE}" DESTINATION "./resources/tools/uv" RENAME "${ORCA_BUNDLED_UV_FILENAME}")
endif()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
install (PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} DESTINATION ".")
@@ -905,6 +1119,9 @@ elseif (SLIC3R_FHS)
install(DIRECTORY ${SLIC3R_RESOURCES_DIR}/ DESTINATION ${SLIC3R_FHS_RESOURCES}
PATTERN "*/udev" EXCLUDE
)
if(ORCA_BUNDLED_UV_EXECUTABLE)
install(PROGRAMS "${ORCA_BUNDLED_UV_EXECUTABLE}" DESTINATION "${SLIC3R_FHS_RESOURCES}/tools/uv" RENAME "${ORCA_BUNDLED_UV_FILENAME}")
endif()
install(FILES src/dev-utils/platform/unix/com.orcaslicer.OrcaSlicer.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
foreach(SIZE 32 128 192)
install(FILES ${SLIC3R_RESOURCES_DIR}/images/OrcaSlicer_${SIZE}px.png
@@ -913,9 +1130,15 @@ elseif (SLIC3R_FHS)
endforeach()
elseif (CMAKE_MACOSX_BUNDLE)
# install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/OrcaSlicer.app/Contents/resources")
if(ORCA_BUNDLED_UV_EXECUTABLE)
install(PROGRAMS "${ORCA_BUNDLED_UV_EXECUTABLE}" DESTINATION "${CMAKE_INSTALL_PREFIX}/OrcaSlicer.app/Contents/Resources/tools/uv" RENAME "${ORCA_BUNDLED_UV_FILENAME}")
endif()
else ()
install(FILES src/dev-utils/platform/unix/com.orcaslicer.OrcaSlicer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications)
install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources")
if(ORCA_BUNDLED_UV_EXECUTABLE)
install(PROGRAMS "${ORCA_BUNDLED_UV_EXECUTABLE}" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources/tools/uv" RENAME "${ORCA_BUNDLED_UV_FILENAME}")
endif()
endif ()
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE.txt DESTINATION ".")
@@ -927,6 +1150,16 @@ set (CPACK_PACKAGE_VERSION_MAJOR "${ORCA_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${ORCA_VERSION_MINOR}")
set (CPACK_PACKAGE_VERSION_PATCH "${ORCA_VERSION_PATCH}")
set (CPACK_PACKAGE_FILE_NAME "OrcaSlicer_Windows_Installer_V${SoftFever_VERSION}")
# Suffix the Windows installer with its target arch so the x64 and arm64 builds
# produce distinct filenames (matches ARCH_SUFFIX in build_orca.yml). Same
# CMAKE_SYSTEM_PROCESSOR mapping used by orcaslicer_copy_dlls() above.
if (WIN32)
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64")
string (APPEND CPACK_PACKAGE_FILE_NAME "_arm64")
else ()
string (APPEND CPACK_PACKAGE_FILE_NAME "_x64")
endif ()
endif ()
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Orca Slicer is an open source slicer for FDM printers")
set (CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/OrcaSlicer/OrcaSlicer")
set (CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})
@@ -934,9 +1167,6 @@ set (CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/resources/images\\\\OrcaSlicer.ico"
set (CPACK_NSIS_MUI_ICON "${CPACK_PACKAGE_ICON}")
set (CPACK_NSIS_MUI_UNIICON "${CPACK_PACKAGE_ICON}")
set (CPACK_NSIS_INSTALLED_ICON_NAME "$INSTDIR\\\\orca-slicer.exe")
set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "
CreateShortCut \\\"$DESKTOP\\\\OrcaSlicer.lnk\\\" \\\"$INSTDIR\\\\orca-slicer.exe\\\"
")
set (CPACK_PACKAGE_CHECKSUM SHA256)
set (CPACK_PACKAGE_INSTALL_REGISTRY_KEY "OrcaSlicer")
set (CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)

View File

@@ -15,15 +15,20 @@ Optimize your prints with ultra-fast slicing, intelligent support generation, an
# Official links and community
#### Official Website:
<a href="https://www.orcaslicer.com/" style="font-size:2em;">OrcaSlicer.com</a>
#### Github Repository:
<a href="https://github.com/OrcaSlicer/OrcaSlicer"><img src="https://img.shields.io/badge/OrcaSlicer-181717?style=flat&logo=github&logoColor=white" width="200" alt="GitHub Logo"/> </a>
#### Follow us:
<a href="https://twitter.com/real_OrcaSlicer"><img src="https://img.shields.io/badge/real__OrcaSlicer-000000?style=flat&logo=x&logoColor=white" width="200" alt="X Logo"/> </a>
<a href="https://twitter.com/real_OrcaSlicer"><img src="https://img.shields.io/badge/real__OrcaSlicer-000000?style=flat&logo=x&logoColor=white" width="200" alt="X Logo"/> </a>
<a href="https://www.youtube.com/@OfficialOrcaSlicer"><img src="https://img.shields.io/badge/OfficialOrcaSlicer-FF0000?style=flat&logo=youtube&logoColor=white" width="200" alt="YouTube Logo"/> </a>
#### Join our Discord community:
<a href="https://discord.gg/P4VE9UY9gJ"><img src="https://img.shields.io/badge/-Discord-5865F2?style=flat&logo=discord&logoColor=fff" width="200" alt="discord logo"/> </a>
<table border="2" style="border-color: #ffa500; background-color:rgb(232, 220, 180); color: #856404;">
@@ -91,17 +96,17 @@ Explore the latest developments in OrcaSlicer with our nightly builds. Feedback
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
- *For convenience there is also a portable build available.*
- *For convenience there is also a portable build available.*
<details>
<summary>Troubleshooting</summary>
- *If you have troubles to run the build, you might need to install following runtimes:*
- [MicrosoftEdgeWebView2RuntimeInstallerX64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe)
- [Details of this runtime](https://aka.ms/webview2)
- [Alternative Download Link Hosted by Microsoft](https://go.microsoft.com/fwlink/p/?LinkId=2124703)
- [vcredist2019_x64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/vcredist2019_x64.exe)
- [Alternative Download Link Hosted by Microsoft](https://aka.ms/vs/17/release/vc_redist.x64.exe)
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
- *If you have troubles to run the build, you might need to install following runtimes:*
- [MicrosoftEdgeWebView2RuntimeInstallerX64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe)
- [Details of this runtime](https://aka.ms/webview2)
- [Alternative Download Link Hosted by Microsoft](https://go.microsoft.com/fwlink/p/?LinkId=2124703)
- [vcredist2019_x64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/vcredist2019_x64.exe)
- [Alternative Download Link Hosted by Microsoft](https://aka.ms/vs/17/release/vc_redist.x64.exe)
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
</details>
Windows Package Manager
@@ -137,9 +142,10 @@ winget install --id=SoftFever.OrcaSlicer -e
![mac_security_setting](./SoftFever_doc/mac_security_setting.png)
</details>
## Linux
## Linux
### Flathub (Recommended)
OrcaSlicer is available through FlatHub:
<a href='https://flathub.org/apps/com.orcaslicer.OrcaSlicer'><img width='240' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.png'/></a>
@@ -154,6 +160,9 @@ flatpak run com.orcaslicer.OrcaSlicer
It can also be installed through graphical software managers (KDE Discover, GNOME Software, etc.) when Flathub is enabled. Search for **OrcaSlicer** in your software center.
### AppImage
AppImages are published for both **x86_64** and **aarch64** (ARM64). Pick the file matching your CPU — the ARM64 build has `aarch64` in its name (e.g. `OrcaSlicer_Linux_AppImage_Ubuntu2404_aarch64_*.AppImage`).
1. Download App image from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
2. Double click the downloaded file to run it.
@@ -185,7 +194,7 @@ resolution: 0.1
Their generous support enables me to purchase filaments and other essential 3D printing materials for the project.
Thank you! :)
## Sponsors:
## Sponsors
<table>
<tr>
@@ -208,8 +217,8 @@ Thank you! :)
## Support me
<a href="https://github.com/sponsors/SoftFever"><img src="https://img.shields.io/badge/GitHub%20Sponsors-30363D?style=flat&logo=GitHub-Sponsors&logoColor=EA4AAA" height="50"></a>
<a href="https://ko-fi.com/G2G5IP3CP"><img src="https://img.shields.io/badge/Support_me_on_Ko--fi-FF5E5B?style=flat&logo=ko-fi&logoColor=white" height="50"></a>
<a href="https://github.com/sponsors/SoftFever"><img src="https://img.shields.io/badge/GitHub%20Sponsors-30363D?style=flat&logo=GitHub-Sponsors&logoColor=EA4AAA" height="50"></a>
<a href="https://ko-fi.com/G2G5IP3CP"><img src="https://img.shields.io/badge/Support_me_on_Ko--fi-FF5E5B?style=flat&logo=ko-fi&logoColor=white" height="50"></a>
<a href="https://paypal.me/softfever3d"><img src="https://img.shields.io/badge/PayPal-003087?style=flat&logo=paypal&logoColor=fff" height="50"></a>
## Some Background
@@ -221,6 +230,7 @@ OrcaSlicer began in that same spirit, drawing from BambuStudio, PrusaSlicer, and
The OrcaSlicer logo was designed by community member [Justin Levine](https://github.com/jal-co).
# License
- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3.
- The **GNU Affero General Public License**, version 3 ensures that if you use any part of this software in any way (even behind a web server), your software must be released under the same license.
- OrcaSlicer includes a **pressure advance calibration pattern test** adapted from Andrew Ellis' generator, which is licensed under GNU General Public License, version 3. Ellis' generator is itself adapted from a generator developed by Sineos for Marlin, which is licensed under GNU General Public License, version 3.

View File

@@ -504,13 +504,24 @@ if [[ -n "${USE_LLD}" ]] ; then
fi
fi
# Auto-detect ccache for faster rebuilds
export CMAKE_CCACHE_ARGS=()
if command -v ccache >/dev/null 2>&1 ; then
echo "ccache found at $(command -v ccache), enabling compiler caching..."
export CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
CMAKE_CCACHE=${CMAKE_CCACHE:-}
if [ -n "$CMAKE_CCACHE" ]; then
echo "Checking ${CMAKE_CCACHE} environment variable for compiler cache program..."
CMAKE_CCACHE=$(command -v "${CMAKE_CCACHE}") || {
echo "CMAKE_CCACHE environment variable is set to '${CMAKE_CCACHE}' but it was not found in PATH."
CMAKE_CCACHE=""
}
elif command -v sccache >/dev/null 2>&1 ; then
CMAKE_CCACHE=$(command -v sccache)
elif command -v ccache >/dev/null 2>&1 ; then
CMAKE_CCACHE=$(command -v ccache)
fi
if [ -n "${CMAKE_CCACHE}" ] ; then
echo "${CMAKE_CCACHE} found, enabling compiler caching..."
export CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_CCACHE}" -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CCACHE}")
else
echo "Note: ccache not found. Install ccache for faster rebuilds."
echo "Note: ccache or sccache are not found. Install either of them for faster rebuilds."
fi
if [[ -n "${BUILD_DEPS}" ]] ; then
@@ -525,7 +536,7 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
BUILD_ARGS+=(-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}")
fi
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}"
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}"
print_and_run cmake --build deps/$BUILD_DIR -j1
fi

View File

@@ -179,6 +179,45 @@ function pack_deps() {
)
}
# --- Bundled Python runtime verification --------------------------------------
# Relocation is handled at the source: deps/python3/python3.cmake stamps
# libpython with an @rpath id and src/CMakeLists.txt gives the app a matching
# rpath. This gate catches regressions that would otherwise only surface as
# launch failures on end users' machines (the absolute deps path still exists
# on the build host, so a plain run can pass while relocation is broken --
# hence the otool checks). The x86_64 leg runs under Rosetta on arm64 hosts.
function verify_python_runtime() {
local app="$1"
local pydir="$app/Contents/MacOS/python"
[ -d "$pydir" ] || return 0 # app doesn't bundle Python (e.g. profile validator)
# Version-agnostic interpreter name so a CPython version bump cannot
# silently skip the gate; if the dir exists the interpreter must too.
local pybin="$pydir/bin/python3"
if [ ! -x "$pybin" ]; then
echo "ERROR: bundled python/ present but no interpreter at $pybin" >&2
exit 1
fi
echo " Verifying bundled Python runtime in $(basename "$app")..."
local bad
bad=$(otool -arch all -L "$pybin" "$app/Contents/MacOS/OrcaSlicer" | grep "libpython" | grep -v "@rpath/" || true)
if [ -n "$bad" ]; then
echo "ERROR: a bundled binary references libpython by absolute path (relocation regression):" >&2
echo "$bad" >&2
exit 1
fi
# otool -L shows load commands only; assert the consumer rpath separately.
# Its loss is masked on the build host by CMake's absolute build-tree rpath.
if ! otool -arch all -l "$app/Contents/MacOS/OrcaSlicer" | grep -q "path @executable_path/python/lib "; then
echo "ERROR: OrcaSlicer lacks the @executable_path/python/lib rpath (relocation regression)" >&2
exit 1
fi
if ! "$pybin" -c "import ssl"; then
echo "ERROR: bundled Python failed to start (libpython relocation broken," >&2
echo " or missing Rosetta 2 for the x86_64 leg?)" >&2
exit 1
fi
}
function build_slicer() {
# iterate over two architectures: x86_64 and arm64
for _ARCH in x86_64 arm64; do
@@ -239,6 +278,8 @@ function build_slicer() {
cp -R "$resources_path" ./OrcaSlicer.app/Contents/Resources
# delete .DS_Store file
find ./OrcaSlicer.app/ -name '.DS_Store' -delete
verify_python_runtime ./OrcaSlicer.app
# Copy OrcaSlicer_profile_validator.app if it exists
if [ -f "../src$BUILD_DIR_CONFIG_SUBDIR/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
@@ -247,6 +288,7 @@ function build_slicer() {
cp -pR "../src$BUILD_DIR_CONFIG_SUBDIR/OrcaSlicer_profile_validator.app" ./OrcaSlicer_profile_validator.app
# delete .DS_Store file
find ./OrcaSlicer_profile_validator.app/ -name '.DS_Store' -delete
verify_python_runtime ./OrcaSlicer_profile_validator.app
fi
)
@@ -302,6 +344,7 @@ function build_universal() {
echo "Creating universal binaries for OrcaSlicer.app..."
lipo_dir "$UNIVERSAL_APP" "$X86_64_APP"
echo "Universal OrcaSlicer.app created at $UNIVERSAL_APP"
verify_python_runtime "$UNIVERSAL_APP"
# Create universal binary for profile validator if it exists
ARM64_VALIDATOR="$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app"

View File

@@ -3,6 +3,17 @@
set WP=%CD%
set _START_TIME=%TIME%
@REM Default target architecture to the host CPU arch; override by passing
@REM "x64" or "arm64" as an argument. PROCESSOR_ARCHITEW6432 covers a 32-bit
@REM shell running on a 64-bit OS, where PROCESSOR_ARCHITECTURE reads "x86".
set arch=x64
if /I "%PROCESSOR_ARCHITECTURE%"=="ARM64" set arch=ARM64
if /I "%PROCESSOR_ARCHITEW6432%"=="ARM64" set arch=ARM64
if /I "%1"=="arm64" set arch=ARM64
if /I "%2"=="arm64" set arch=ARM64
if /I "%1"=="x64" set arch=x64
if /I "%2"=="x64" set arch=x64
@REM Check for Ninja Multi-Config option (-x)
set USE_NINJA=0
for %%a in (%*) do (
@@ -68,12 +79,13 @@ echo Using CMake generator: %CMAKE_GENERATOR%
@REM Pack deps
if "%1"=="pack" (
setlocal ENABLEDELAYEDEXPANSION
setlocal ENABLEDELAYEDEXPANSION
cd %WP%/deps/build
if "%arch%"=="ARM64" cd %WP%/deps/build-arm64
for /f "tokens=2-4 delims=/ " %%a in ('date /t') do set build_date=%%c%%b%%a
echo packing deps: OrcaSlicer_dep_win64_!build_date!_vs!VS_VERSION!.zip
echo packing deps: OrcaSlicer_dep_win-!arch!_!build_date!_vs!VS_VERSION!.zip
%WP%/tools/7z.exe a OrcaSlicer_dep_win64_!build_date!_vs!VS_VERSION!.zip OrcaSlicer_dep
%WP%/tools/7z.exe a OrcaSlicer_dep_win-!arch!_!build_date!_vs!VS_VERSION!.zip OrcaSlicer_dep
goto :done
)
@@ -95,9 +107,10 @@ if "%debug%"=="ON" (
set build_dir=build
)
)
echo build type set to %build_type%
if "%arch%"=="ARM64" set build_dir=%build_dir%-arm64
echo build type set to %build_type%, arch=%arch%
setlocal DISABLEDELAYEDEXPANSION
setlocal DISABLEDELAYEDEXPANSION
cd deps
mkdir %build_dir%
cd %build_dir%
@@ -116,7 +129,7 @@ if "%USE_NINJA%"=="1" (
cmake ../ -G %CMAKE_GENERATOR% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target deps
) else (
cmake ../ -G %CMAKE_GENERATOR% -A x64 -DCMAKE_BUILD_TYPE=%build_type%
cmake ../ -G %CMAKE_GENERATOR% -A %arch% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target deps -- -m
)
@echo off
@@ -135,7 +148,7 @@ if "%USE_NINJA%"=="1" (
cmake .. -G %CMAKE_GENERATOR% -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target ALL_BUILD
) else (
cmake .. -G %CMAKE_GENERATOR% -A x64 -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake .. -G %CMAKE_GENERATOR% -A %arch% -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target ALL_BUILD -- -m
)
@echo off

View File

@@ -15,10 +15,18 @@ if "%1"=="pack" (
set debug=OFF
set debuginfo=OFF
@REM Default target architecture to the host CPU arch; override with x64/arm64 arg.
set arch=x64
if /I "%PROCESSOR_ARCHITECTURE%"=="ARM64" set arch=ARM64
if /I "%PROCESSOR_ARCHITEW6432%"=="ARM64" set arch=ARM64
if "%1"=="debug" set debug=ON
if "%2"=="debug" set debug=ON
if "%1"=="debuginfo" set debuginfo=ON
if "%2"=="debuginfo" set debuginfo=ON
if /I "%1"=="arm64" set arch=ARM64
if /I "%2"=="arm64" set arch=ARM64
if /I "%1"=="x64" set arch=x64
if /I "%2"=="x64" set arch=x64
if "%debug%"=="ON" (
set build_type=Debug
set build_dir=build-dbg
@@ -31,7 +39,8 @@ if "%debug%"=="ON" (
set build_dir=build
)
)
echo build type set to %build_type%
if "%arch%"=="ARM64" set build_dir=%build_dir%-arm64
echo build type set to %build_type%, arch=%arch%
setlocal DISABLEDELAYEDEXPANSION
cd deps
@@ -48,7 +57,7 @@ echo "building deps.."
echo on
REM Set minimum CMake policy to avoid <3.5 errors
set CMAKE_POLICY_VERSION_MINIMUM=3.5
cmake ../ -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=%build_type%
cmake ../ -G "Visual Studio 17 2022" -A %arch% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target deps -- -m
@echo off
@@ -62,7 +71,7 @@ cd %build_dir%
echo on
set CMAKE_POLICY_VERSION_MINIMUM=3.5
cmake .. -G "Visual Studio 17 2022" -A x64 -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake .. -G "Visual Studio 17 2022" -A %arch% -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target ALL_BUILD -- -m
@echo off
cd ..

View File

@@ -124,6 +124,8 @@ endif()
if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "x64" OR "${CMAKE_GENERATOR}" MATCHES "Win64")
set(_arch "x64")
elseif("${CMAKE_GENERATOR_PLATFORM}" MATCHES "ARM64")
set(_arch "x64") # GLEW ships one header set; ARM64 uses the x64 import path
else()
set(_arch "Win32")
endif()

View File

@@ -10,7 +10,22 @@ if (APPLE AND CMAKE_OSX_ARCHITECTURES)
set(_context_arch_line "-DBOOST_CONTEXT_ARCHITECTURE:STRING=${CMAKE_OSX_ARCHITECTURES}")
endif ()
# Windows ARM64: Boost.Context's default fcontext implementation assembles .asm
# via armasm64, which trips a CMake ASM_ARMASM linker-module bug under the VS
# generator. The winfib implementation (Windows Fiber API) avoids assembly while
# keeping the Boost::context target that Boost.Asio's stackful coroutines need.
set(_context_impl_line "")
if (MSVC AND "${DEPS_ARCH}" STREQUAL "arm64")
set(_context_impl_line "-DBOOST_CONTEXT_IMPLEMENTATION:STRING=winfib")
endif ()
set(_options "")
if (MSVC AND DEP_DEBUG)
set(_options "FORWARD_CONFIG")
endif ()
orcaslicer_add_cmake_project(Boost
${_options}
URL "https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.gz"
URL_HASH SHA256=4d27e9efed0f6f152dc28db6430b9d3dfb40c0345da7342eaa5a987dde57bd95
LIST_SEPARATOR |
@@ -22,6 +37,7 @@ orcaslicer_add_cmake_project(Boost
-DBOOST_IOSTREAMS_ENABLE_ZSTD:BOOL=OFF
"${_context_abi_line}"
"${_context_arch_line}"
"${_context_impl_line}"
)
set(DEP_Boost_DEPENDS ZLIB)

26
deps/CMakeLists.txt vendored
View File

@@ -155,17 +155,25 @@ if (NOT _is_multi AND NOT CMAKE_BUILD_TYPE)
endif ()
function(orcaslicer_add_cmake_project projectname)
cmake_parse_arguments(P_ARGS "" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN})
cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN})
set(_configs_line -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
if (_is_multi OR MSVC)
if (ORCA_INCLUDE_DEBUG_INFO AND NOT DEP_DEBUG)
if (P_ARGS_FORWARD_CONFIG)
set(_configs_line -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
elseif (ORCA_INCLUDE_DEBUG_INFO AND NOT DEP_DEBUG)
set(_configs_line "-DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELWITHDEBINFO} -DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
else ()
set(_configs_line "")
endif ()
endif ()
if (P_ARGS_FORWARD_CONFIG)
set(_target_config "$<CONFIG>")
else()
set(_target_config "Release")
endif()
if (MSVC)
set(_gen CMAKE_GENERATOR "${DEP_MSVC_GEN}" CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}")
else()
@@ -196,6 +204,8 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
-DCMAKE_DEBUG_POSTFIX:STRING=d
-DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_COMPILER_LAUNCHER}
-DCMAKE_TOOLCHAIN_FILE:STRING=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS}
-DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS}
@@ -206,8 +216,8 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
${DEP_CMAKE_OPTS}
${P_ARGS_CMAKE_ARGS}
${P_ARGS_UNPARSED_ARGUMENTS}
BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release -- ${_build_j}
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Release
BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${_target_config} -- ${_build_j}
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${_target_config}
)
if (FLATPAK)
@@ -241,14 +251,16 @@ else()
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
-DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_COMPILER_LAUNCHER}
-DBUILD_SHARED_LIBS:BOOL=OFF
${_cmake_osx_arch}
"${_configs_line}"
${DEP_CMAKE_OPTS}
${P_ARGS_CMAKE_ARGS}
${P_ARGS_UNPARSED_ARGUMENTS}
BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release -- ${_build_j}
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Release
BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${_target_config} -- ${_build_j}
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${_target_config}
)
endif()
@@ -410,6 +422,7 @@ endif ()
include(OCCT/OCCT.cmake)
include(OpenCV/OpenCV.cmake)
include(python3/python3.cmake)
set(_dep_list
dep_Boost
@@ -432,6 +445,7 @@ set(_dep_list
${ZLIB_PKG}
${EXPAT_PKG}
dep_libnoise
dep_python3
)
if (MSVC)

View File

@@ -1,4 +1,10 @@
set(_options "")
if (MSVC AND DEP_DEBUG)
set(_options "FORWARD_CONFIG")
endif ()
orcaslicer_add_cmake_project(Draco
${_options}
URL https://github.com/google/draco/archive/refs/tags/1.5.7.zip
URL_HASH SHA256=27b72ba2d5ff3d0a9814ad40d4cb88f8dc89a35491c0866d952473f8f9416b77
)

View File

@@ -1,5 +1,11 @@
set(_eigen_extra_flags "")
if (MSVC)
set(_eigen_extra_flags "-DCMAKE_CXX_FLAGS:STRING=/bigobj")
endif ()
orcaslicer_add_cmake_project(Eigen
URL https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.zip
URL_HASH SHA256=0dbb1f9e3aaad66f352c03227d8c983f6f0b49e0b07e71a7300f4abcc01aee12
CMAKE_ARGS "${_eigen_extra_flags}"
DEPENDS dep_Boost dep_GMP dep_MPFR
)

BIN
deps/GMP/gmp/lib/win-arm64/libgmp-10.dll vendored Normal file

Binary file not shown.

BIN
deps/GMP/gmp/lib/win-arm64/libgmp-10.lib vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,7 +1,13 @@
if (MSVC)
# Intel IPP / IPP-ICV is x86/x64 only — there is no ARM64 build, so enabling it
# leaves ~200 unresolved ippicv* externals at link time on Windows ARM64.
if (MSVC AND NOT "${DEPS_ARCH}" STREQUAL "arm64")
set(_use_IPP "-DWITH_IPP=ON")
if (DEP_DEBUG)
set(_options "FORWARD_CONFIG")
endif ()
else ()
set(_use_IPP "-DWITH_IPP=OFF")
set(_options "")
endif ()
if (IN_GIT_REPO)
@@ -9,6 +15,7 @@ if (IN_GIT_REPO)
endif ()
orcaslicer_add_cmake_project(OpenCV
${_options}
URL https://github.com/opencv/opencv/archive/refs/tags/4.6.0.tar.gz
URL_HASH SHA256=1ec1cba65f9f20fe5a41fda1586e01c70ea0c9a6d7b67c9e13edf0cfe2239277
PATCH_COMMAND git apply ${OpenCV_DIRECTORY_FLAG} --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-vs.patch ${CMAKE_CURRENT_LIST_DIR}/0002-clang19-macos.patch

View File

@@ -32,6 +32,17 @@ else()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(_patch_cmd ${PATCH_CMD} ${CMAKE_CURRENT_LIST_DIR}/0001-OpenEXR-GCC13.patch)
elseif (MSVC AND "${DEPS_ARCH}" STREQUAL "arm64")
# Windows ARM64: OpenEXR 2.5.5 hard-codes IMF_HAVE_SSE2 for any MSVC
# (ImfSimd.h: `_MSC_VER >= 1300`), pulling in <emmintrin.h> (x86-only) -> C1189.
# Patch the header to require an x86 target, and force the SSE cache vars off.
set(_patch_cmd ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/patch_openexr_arm64.cmake)
set(_openexr_arm64_args
-DOPENEXR_IMF_HAVE_SSE2:BOOL=OFF
-DOPENEXR_IMF_HAVE_SSSE3:BOOL=OFF
-DILMBASE_HAVE_SSE:BOOL=OFF
-DILMBASE_FORCE_DISABLE_INTEL_SSE:BOOL=ON
)
else ()
set(_patch_cmd "")
endif ()
@@ -49,6 +60,7 @@ orcaslicer_add_cmake_project(OpenEXR
-DPYILMBASE_ENABLE:BOOL=OFF
-DOPENEXR_VIEWERS_ENABLE:BOOL=OFF
-DOPENEXR_BUILD_UTILS:BOOL=OFF
${_openexr_arm64_args}
)
endif()

29
deps/OpenEXR/patch_openexr_arm64.cmake vendored Normal file
View File

@@ -0,0 +1,29 @@
# Applied as PATCH_COMMAND for OpenEXR 2.5.5 on Windows ARM64.
#
# Root cause of the ARM64 build failure: OpenEXR/IlmImf/ImfSimd.h hard-codes
# #if defined __SSE2__ || (_MSC_VER >= 1300 && !_M_CEE_PURE)
# #define IMF_HAVE_SSE2 1
# #endif
# The `_MSC_VER >= 1300` arm is true for *every* MSVC, including ARM64, so
# IMF_HAVE_SSE2 gets defined and <emmintrin.h> (an x86-only header) is pulled
# in -> error C1189. This is a pure-preprocessor decision, so no CMake cache
# variable can suppress it. Patch the header to also require an x86 target.
set(_simd "OpenEXR/IlmImf/ImfSimd.h")
if(EXISTS "${_simd}")
file(READ "${_simd}" _content)
set(_old "#if defined __SSE2__ || (_MSC_VER >= 1300 && !_M_CEE_PURE)")
set(_new "#if (defined __SSE2__ || (_MSC_VER >= 1300 && !_M_CEE_PURE)) && (defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__))")
if(_content MATCHES "_M_IX86")
message(STATUS "[ARM64 patch] ImfSimd.h already guarded")
else()
string(REPLACE "${_old}" "${_new}" _patched "${_content}")
if(_patched STREQUAL _content)
message(FATAL_ERROR "[ARM64 patch] Failed to match SSE2 guard in ${_simd}")
endif()
file(WRITE "${_simd}" "${_patched}")
message(STATUS "[ARM64 patch] Guarded IMF_HAVE_SSE2 with x86 arch check in ${_simd}")
endif()
else()
message(FATAL_ERROR "[ARM64 patch] Not found: ${_simd}")
endif()

View File

@@ -6,7 +6,11 @@ if(DEFINED OPENSSL_ARCH)
set(_cross_arch ${OPENSSL_ARCH})
else()
if(WIN32)
set(_cross_arch "VC-WIN64A")
if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "ARM64")
set(_cross_arch "VC-WIN64-ARM")
else()
set(_cross_arch "VC-WIN64A")
endif()
elseif(APPLE)
set(_cross_arch "darwin64-${CMAKE_OSX_ARCHITECTURES}-cc")
endif()

238
deps/python3/python3.cmake vendored Normal file
View File

@@ -0,0 +1,238 @@
include(ProcessorCount)
ProcessorCount(NPROC)
set(_python_version "3.12.3")
string(REGEX REPLACE "^([0-9]+\\.[0-9]+)\\..*" "\\1" _python_version_short "${_python_version}")
set(_python_url "https://www.python.org/ftp/python/${_python_version}/Python-${_python_version}.tar.xz")
set(_python_sha256 "56bfef1fdfc1221ce6720e43a661e3eb41785dd914ce99698d8c7896af4bdaa1")
if(WIN32)
if(MSVC_VERSION EQUAL 1800)
set(_python_platform_toolset v120)
elseif(MSVC_VERSION EQUAL 1900)
set(_python_platform_toolset v140)
elseif(MSVC_VERSION LESS 1920)
set(_python_platform_toolset v141)
elseif(MSVC_VERSION LESS 1930)
set(_python_platform_toolset v142)
elseif(MSVC_VERSION LESS 1950)
set(_python_platform_toolset v143)
elseif(MSVC_VERSION LESS 1960)
set(_python_platform_toolset v145)
else()
message(FATAL_ERROR "Unsupported MSVC version for CPython build: ${MSVC_VERSION}")
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
set(_python_pcbuild_platform ARM64)
set(_python_layout_arch arm64)
set(_python_pcbuild_output_dir arm64)
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_python_pcbuild_platform x64)
set(_python_layout_arch amd64)
set(_python_pcbuild_output_dir amd64)
else()
set(_python_pcbuild_platform Win32)
set(_python_layout_arch win32)
set(_python_pcbuild_output_dir win32)
endif()
set(_python_pcbuild_config Release)
set(_python_layout_debug OFF)
if(DEFINED DEP_DEBUG AND DEP_DEBUG)
set(_python_pcbuild_config Debug)
set(_python_layout_debug ON)
endif()
set(_conf_cmd
cmd /c "echo /p:PlatformToolset=${_python_platform_toolset}>PCbuild\\msbuild.rsp"
)
set(_build_cmd
${CMAKE_COMMAND} -E env "GIT_CEILING_DIRECTORIES=<SOURCE_DIR>/.."
cmd /c PCbuild\\build.bat
-p ${_python_pcbuild_platform}
-c ${_python_pcbuild_config}
--no-tkinter
)
set(_install_cmd
${CMAKE_COMMAND}
-DPYTHON_SOURCE_DIR=<SOURCE_DIR>
-DPYTHON_BUILD_DIR=<SOURCE_DIR>/PCbuild/${_python_pcbuild_output_dir}
-DPYTHON_DEST_DIR=${DESTDIR}/libpython
-DPYTHON_LAYOUT_ARCH=${_python_layout_arch}
-DPYTHON_DEBUG=${_python_layout_debug}
-P ${CMAKE_CURRENT_LIST_DIR}/stage_windows.cmake
)
elseif(APPLE)
# macOS configuration
if(CMAKE_OSX_ARCHITECTURES)
set(_python_target_arch "${CMAKE_OSX_ARCHITECTURES}")
else()
set(_python_target_arch "${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|arm64|aarch64")
set(_python_build_arch aarch64)
set(_python_build_arch_flag "arm64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64")
set(_python_build_arch x86_64)
set(_python_build_arch_flag "x86_64")
else()
set(_python_build_arch "${CMAKE_SYSTEM_PROCESSOR}")
set(_python_build_arch_flag "${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(_python_target_arch MATCHES "ARM64|arm64|aarch64")
set(_python_host_arch aarch64)
set(_python_arch_flag "arm64")
elseif(_python_target_arch MATCHES "x86_64|AMD64|amd64")
set(_python_host_arch x86_64)
set(_python_arch_flag "x86_64")
else()
message(FATAL_ERROR "Unsupported macOS Python target architecture: ${_python_target_arch}")
endif()
set(_python_arch_flags "-arch ${_python_arch_flag} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
# No -rpath: all other deps are static, so libpython has no shared
# dependencies to find there. headerpad reserves load-command space for
# the post-install -add_rpath below.
set(_python_ldflags "${_python_arch_flags} -Wl,-headerpad_max_install_names")
if(IS_CROSS_COMPILE)
set(_python_build_tgt --build=${_python_build_arch}-apple-darwin --host=${_python_host_arch}-apple-darwin)
set(_python_build_arch_flags "-arch ${_python_build_arch_flag} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
set(_python_build_ldflags "${_python_build_arch_flags} -Wl,-rpath,${DESTDIR}/lib")
set(_python_build_python_dir "<SOURCE_DIR>/build-python-host")
set(_python_build_python "${_python_build_python_dir}/python")
set(_conf_cmd
/bin/sh -c
"rm -rf '${_python_build_python_dir}' && \
mkdir -p '${_python_build_python_dir}' && \
cd '${_python_build_python_dir}' && \
env \
CC='${CMAKE_C_COMPILER}' \
CXX='${CMAKE_CXX_COMPILER}' \
CFLAGS='${_python_build_arch_flags}' \
CXXFLAGS='${_python_build_arch_flags}' \
LDFLAGS='${_python_build_ldflags}' \
MACOSX_DEPLOYMENT_TARGET='${CMAKE_OSX_DEPLOYMENT_TARGET}' \
../configure \
--prefix='${_python_build_python_dir}/install' \
--enable-shared \
--without-static-libpython \
--disable-test-modules \
--build=${_python_build_arch}-apple-darwin && \
make -j${NPROC} python && \
cd '<SOURCE_DIR>' && \
env \
CC='${CMAKE_C_COMPILER}' \
CXX='${CMAKE_CXX_COMPILER}' \
CFLAGS='${_python_arch_flags}' \
CXXFLAGS='${_python_arch_flags}' \
LDFLAGS='${_python_ldflags}' \
MACOSX_DEPLOYMENT_TARGET='${CMAKE_OSX_DEPLOYMENT_TARGET}' \
./configure \
--prefix='${DESTDIR}/libpython' \
--enable-shared \
--enable-optimizations \
--without-static-libpython \
--with-openssl='${DESTDIR}' \
--disable-test-modules \
${_python_build_tgt} \
--with-build-python='${_python_build_python}' \
py_cv_module__tkinter=n/a"
)
else()
set(_python_build_tgt --build=${_python_host_arch}-apple-darwin)
set(_conf_cmd
env
"CC=${CMAKE_C_COMPILER}"
"CXX=${CMAKE_CXX_COMPILER}"
"CFLAGS=${_python_arch_flags}"
"CXXFLAGS=${_python_arch_flags}"
"LDFLAGS=${_python_ldflags}"
"MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}"
./configure
--prefix=${DESTDIR}/libpython
--enable-shared
--enable-optimizations
--without-static-libpython
--with-openssl=${DESTDIR}
--disable-test-modules
${_python_build_tgt}
# Tcl/Tk 9.0 (e.g. from Homebrew) is incompatible with CPython 3.12's
# _tkinter; OrcaSlicer's embedded Python does not need tkinter anyway.
py_cv_module__tkinter=n/a
)
endif()
set(_build_cmd make -j${NPROC})
# CPython stamps libpython with an absolute install name ($prefix/lib/...),
# which every consumer inherits at link time and which only exists on the
# build host. Normalize once here, before anything links against the dep:
# give the dylib an @rpath id and teach the interpreter to find it relative
# to itself. Consumers then just need an rpath entry (src/CMakeLists.txt).
# install_name_tool invalidates code signatures, so re-sign ad-hoc; CI
# re-signs the whole bundle with the real identity later.
# ld collapses '//' in -install_name (but not in -rpath) strings, while
# ${DESTDIR} ends with a slash -- collapse slashes so -change matches the
# recorded install name.
string(REGEX REPLACE "/+" "/" _python_prefix "${DESTDIR}/libpython")
set(_python_dylib "${_python_prefix}/lib/libpython${_python_version_short}.dylib")
set(_python_bin "${_python_prefix}/bin/python${_python_version_short}")
set(_install_cmd make install
COMMAND install_name_tool -id "@rpath/libpython${_python_version_short}.dylib" "${_python_dylib}"
COMMAND install_name_tool -change "${_python_dylib}" "@rpath/libpython${_python_version_short}.dylib" "${_python_bin}"
COMMAND install_name_tool -add_rpath "@loader_path/../lib" "${_python_bin}"
COMMAND codesign --force --sign - "${_python_dylib}"
COMMAND codesign --force --sign - "${_python_bin}"
)
else()
# Linux/Unix
# Kept verbatim, no slash normalization (unlike the macOS branch's
# collapsed copy): the LDFLAGS rpath below is recorded byte-for-byte in
# the ELF, and the OLD_RPATH handed to relocate_linux.cmake must match it
# exactly -- both derive from this one variable to make that structural.
set(_python_prefix "${DESTDIR}/libpython")
# The rpath points at libpython's real install dir, so the interpreter runs
# in-tree pre-relocation -- and, critically, it reserves enough RUNPATH
# bytes for the in-place $ORIGIN rewrite at install time (Flatpak's
# DESTDIR is the short /app) -- see relocate_linux.cmake.
set(_conf_cmd ./configure
--prefix=${_python_prefix}
--enable-shared
--enable-optimizations
--with-openssl=${DESTDIR}
--without-static-libpython
--disable-test-modules
# Tcl/Tk 9.0 is incompatible with CPython 3.12's _tkinter; not needed here.
py_cv_module__tkinter=n/a
LDFLAGS=-Wl,-rpath,${_python_prefix}/lib
)
set(_build_cmd make -j${NPROC})
set(_install_cmd make install
COMMAND ${CMAKE_COMMAND}
"-DPYTHON_BIN=${_python_prefix}/bin/python${_python_version_short}"
"-DOLD_RPATH=${_python_prefix}/lib"
-P "${CMAKE_CURRENT_LIST_DIR}/relocate_linux.cmake"
)
endif()
ExternalProject_Add(dep_python3
URL "${_python_url}"
URL_HASH SHA256=${_python_sha256}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/python3
BUILD_IN_SOURCE ON
CONFIGURE_COMMAND ${_conf_cmd}
BUILD_COMMAND ${_build_cmd}
INSTALL_COMMAND ${_install_cmd}
)
# Python depends on OpenSSL and ZLIB
if(TARGET dep_OpenSSL)
add_dependencies(dep_python3 dep_OpenSSL)
endif()
if(TARGET dep_ZLIB)
add_dependencies(dep_python3 dep_ZLIB)
endif()

10
deps/python3/relocate_linux.cmake vendored Normal file
View File

@@ -0,0 +1,10 @@
# Repoint the installed interpreter's RUNPATH from the absolute deps dir to a
# self-relative entry so the bundled runtime is relocatable (the deps tree,
# Flatpak /app/libpython, and AppImage $APPDIR/lib/python all keep bin/ and
# lib/ as siblings). $ORIGIN is expanded by the dynamic loader; CMake leaves
# it alone (only ${...} is expanded here). RPATH_CHANGE edits the ELF in
# place, so the new entry must not be longer than the old one: the reserved
# ${DESTDIR}/libpython/lib is at least 18 bytes even for the shortest
# supported DESTDIR (Flatpak's /app), longer than the 14-byte $ORIGIN/../lib.
# Invoked from python3.cmake with -DPYTHON_BIN=... -DOLD_RPATH=...
file(RPATH_CHANGE FILE "${PYTHON_BIN}" OLD_RPATH "${OLD_RPATH}" NEW_RPATH "$ORIGIN/../lib")

72
deps/python3/stage_windows.cmake vendored Normal file
View File

@@ -0,0 +1,72 @@
cmake_minimum_required(VERSION 3.13)
set(_python_abi "312")
foreach(_var PYTHON_SOURCE_DIR PYTHON_BUILD_DIR PYTHON_DEST_DIR PYTHON_LAYOUT_ARCH)
if(NOT DEFINED ${_var} OR "${${_var}}" STREQUAL "")
message(FATAL_ERROR "${_var} is required")
endif()
endforeach()
set(_python_exe "${PYTHON_BUILD_DIR}/python.exe")
if(PYTHON_DEBUG)
set(_python_exe "${PYTHON_BUILD_DIR}/python_d.exe")
endif()
if(NOT EXISTS "${_python_exe}")
message(FATAL_ERROR "Built Python executable not found: ${_python_exe}")
endif()
file(REMOVE_RECURSE "${PYTHON_DEST_DIR}")
file(MAKE_DIRECTORY "${PYTHON_DEST_DIR}")
# CPython's Windows layout helper reads LICENSE.txt from the build output.
# Source archives ship this file as LICENSE, so provide the expected name.
if(EXISTS "${PYTHON_SOURCE_DIR}/LICENSE" AND NOT EXISTS "${PYTHON_BUILD_DIR}/LICENSE.txt")
configure_file("${PYTHON_SOURCE_DIR}/LICENSE" "${PYTHON_BUILD_DIR}/LICENSE.txt" COPYONLY)
endif()
execute_process(
COMMAND
"${CMAKE_COMMAND}" -E env
"PYTHONHOME="
"PYTHONPATH=${PYTHON_SOURCE_DIR}/Lib"
"${_python_exe}"
"${PYTHON_SOURCE_DIR}/PC/layout"
--source "${PYTHON_SOURCE_DIR}"
--build "${PYTHON_BUILD_DIR}"
--arch "${PYTHON_LAYOUT_ARCH}"
--copy "${PYTHON_DEST_DIR}"
--include-dev
WORKING_DIRECTORY "${PYTHON_SOURCE_DIR}"
RESULT_VARIABLE _layout_result
)
if(NOT _layout_result EQUAL 0)
message(FATAL_ERROR "CPython Windows layout staging failed with exit code ${_layout_result}")
endif()
set(_required_files
"${PYTHON_DEST_DIR}/Lib/encodings/__init__.py"
"${PYTHON_DEST_DIR}/include/Python.h"
)
if(PYTHON_DEBUG)
list(APPEND _required_files
"${PYTHON_DEST_DIR}/python_d.exe"
"${PYTHON_DEST_DIR}/python${_python_abi}_d.dll"
"${PYTHON_DEST_DIR}/libs/python${_python_abi}_d.lib"
)
else()
list(APPEND _required_files
"${PYTHON_DEST_DIR}/python.exe"
"${PYTHON_DEST_DIR}/python${_python_abi}.dll"
"${PYTHON_DEST_DIR}/libs/python${_python_abi}.lib"
)
endif()
foreach(_required_file IN LISTS _required_files)
if(NOT EXISTS "${_required_file}")
message(FATAL_ERROR "Staged Python file missing: ${_required_file}")
endif()
endforeach()

View File

@@ -27,6 +27,7 @@ add_subdirectory(libigl)
add_subdirectory(libnest2d)
add_subdirectory(mcut)
add_subdirectory(md4c)
add_subdirectory(mdns)
add_subdirectory(miniz)
add_subdirectory(minilzo)
add_subdirectory(qhull)

View File

@@ -267,7 +267,7 @@ void ImGui::Text(const char* fmt, ...)
void ImGui::TextCentered(const char* text, ...)
{
va_list vaList;
va_start(vaList,&text);
va_start(vaList, text);
float font_size = ImGui::GetFontSize() * strlen(text) / 2;
ImGui::SameLine(ImGui::GetCursorPos().x / 2 - font_size + (font_size / 2));

View File

@@ -1123,18 +1123,17 @@ private:
std::vector<RawShape> objs,excludes;
for (const Item &item : items_) {
if (item.isFixed()) continue;
objs.push_back(item.transformedShape());
if (item.isFixed())
excludes.push_back(item.transformedShape());
else
objs.push_back(item.transformedShape());
}
if (objs.empty())
return;
// Without fixed items this inner-fit NFP can exceed clipper's range and crash MSVC.
if (!excludes.empty())
{ // find a best position inside NFP of fixed items (excluded regions), so the center of pile is cloest to bed center
RawShape objs_convex_hull = sl::convexHull(objs);
for (const Item &item : items_) {
if (item.isFixed()) {
excludes.push_back(item.transformedShape());
}
}
auto nfps = calcnfp(objs_convex_hull, excludes, bbin, Lvl<MaxNfpLevel::value>());
if (nfps.empty()) {

View File

@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.13)
project(mdns)
# Static library wrapping mjansson/mdns (public domain) plus the cxmdns C++
# wrapper from CrealityPrint v7.1.1 (AGPL-3.0). See NOTICE.md for attribution.
add_library(mdns STATIC
mdns.h
mdns.c
cxmdns.h
cxmdns.cpp
)
target_include_directories(mdns SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
if (MSVC)
# mjansson/mdns uses GetAdaptersAddresses (Iphlpapi) and Winsock2 (Ws2_32).
target_link_libraries(mdns PUBLIC Iphlpapi Ws2_32)
endif()

39
deps_src/mdns/NOTICE.md Normal file
View File

@@ -0,0 +1,39 @@
# mDNS / DNS-SD library
The four files in this directory implement mDNS / DNS-SD lookup and are
vendored from third-party sources:
## mdns.h, mdns.c
mDNS / DNS-SD lookup library by Mattias Jansson. Originally released to
the public domain at https://github.com/mjansson/mdns.
The exact files here were taken from CrealityOfficial/CrealityPrint
v7.1.1, which split the upstream header-only library into separate
declaration (mdns.h) and implementation (mdns.c) files.
- Source: https://github.com/mjansson/mdns
- License: Public domain (no restrictions on use)
## cxmdns.h, cxmdns.cpp
Thin C++ wrapper over mdns.{h,c} that exposes a single function:
std::vector<machine_info> syncDiscoveryService(
const std::vector<std::string>& prefix);
It sends a DNS-SD meta-discovery query (`_services._dns-sd._udp.local.`),
listens for ~5 seconds, and returns `{ip, service_name}` for every
service announcement whose name contains any of the given prefixes.
OrcaSlicer uses this to find Creality K-series printers on the LAN
(service-name prefix "Creality"), since K-series firmware announces
each printer under a per-device-unique service type
`_Creality-<MAC-derived-hex>._udp.local.` that no fixed-name query can
target.
- Source: CrealityOfficial/CrealityPrint v7.1.1
`src/slic3r/GUI/print_manage/utils/cxmdns.{h,cpp}`
- License: GNU AGPL-3.0 (compatible with OrcaSlicer's AGPL-3.0; see
top-level LICENSE.txt)
- Imported: 2026-05-19

256
deps_src/mdns/cxmdns.cpp Normal file
View File

@@ -0,0 +1,256 @@
#include"cxmdns.h"
#include"mdns.h"
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS 1
#endif
#include <stdio.h>
#include<string.h>
#include <errno.h>
#include <signal.h>
#ifdef _WIN32
#include <winsock2.h>
#include <iphlpapi.h>
#define sleep(x) Sleep(x * 1000)
#else
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#endif
// Alias some things to simulate recieving data to fuzz library
#if defined(MDNS_FUZZING)
#define recvfrom(sock, buffer, capacity, flags, src_addr, addrlen) ((mdns_ssize_t)capacity)
#define printf
#endif
#include "mdns.h"
#if defined(MDNS_FUZZING)
#undef recvfrom
#endif
namespace cxnet
{
template <typename F>
mdns_record_callback_fn lambda2function(F lambda)
{
static auto lambdabak = lambda;
return [](int sock, const struct sockaddr* from, size_t addrlen,
mdns_entry_type_t entry, uint16_t query_id, uint16_t rtype,
uint16_t rclass, uint32_t ttl, const void* data, size_t size,
size_t name_offset, size_t name_length, size_t record_offset,
size_t record_length, void* user_data)->int {lambdabak(sock, from, addrlen, entry, query_id, rtype, rclass, ttl, data, size, name_offset, name_length, record_offset, record_length, user_data); return 0; };
}
volatile sig_atomic_t running = 1;
#ifdef _WIN32
BOOL console_handler(DWORD signal) {
if (signal == CTRL_C_EVENT) {
running = 0;
}
return TRUE;
}
#else
void signal_handler(int signal) {
running = 0;
}
#endif
void recvMachineInfoFromSocket(int sock, void* buffer, size_t capacity, const std::vector<std::string>& prefix, std::vector<machine_info>& retmachineInfos, int recIndex)
{
struct sockaddr_in6 addr;
struct sockaddr* saddr = (struct sockaddr*)&addr;
socklen_t addrlen = sizeof(addr);
memset(&addr, 0, sizeof(addr));
#ifdef __APPLE__
saddr->sa_len = sizeof(addr);
#endif
mdns_ssize_t ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen);
if (ret <= 0)
return;
size_t data_size = (size_t)ret;
//size_t records = 0;
const uint16_t* data = (uint16_t*)buffer;
uint16_t query_id = mdns_ntohs(data++);
uint16_t flags = mdns_ntohs(data++);
uint16_t questions = mdns_ntohs(data++);
uint16_t answer_rrs = mdns_ntohs(data++);
uint16_t authority_rrs = mdns_ntohs(data++);
uint16_t additional_rrs = mdns_ntohs(data++);
// According to RFC 6762 the query ID MUST match the sent query ID (which is 0 in our case)
if (query_id || (flags != 0x8400))
return; // Not a reply to our question
// It seems some implementations do not fill the correct questions field,
// so ignore this check for now and only validate answer string
// if (questions != 1)
// return 0;
int i;
for (i = 0; i < questions; ++i) {
size_t offset = MDNS_POINTER_DIFF(data, buffer);
size_t verify_offset = 12;
// Verify it's our question, _services._dns-sd._udp.local.
if (!mdns_string_equal(buffer, data_size, &offset, mdns_services_query,
sizeof(mdns_services_query), &verify_offset))
return;
data = (const uint16_t*)MDNS_POINTER_OFFSET(buffer, offset);
uint16_t rtype = mdns_ntohs(data++);
uint16_t rclass = mdns_ntohs(data++);
// Make sure we get a reply based on our PTR question for class IN
if ((rtype != MDNS_RECORDTYPE_PTR) || ((rclass & 0x7FFF) != MDNS_CLASS_IN))
return;
}
for (i = 0; i < answer_rrs; ++i) {
size_t offset = MDNS_POINTER_DIFF(data, buffer);
size_t verify_offset = 12;
// Verify it's an answer to our question, _services._dns-sd._udp.local.
size_t name_offset = offset;
int is_answer = mdns_string_equal(buffer, data_size, &offset, mdns_services_query,
sizeof(mdns_services_query), &verify_offset);
if (!is_answer && !mdns_string_skip(buffer, data_size, &offset))
break;
size_t name_length = offset - name_offset;
if ((offset + 10) > data_size)
return;
data = (const uint16_t*)MDNS_POINTER_OFFSET(buffer, offset);
uint16_t rtype = mdns_ntohs(data++);
uint16_t rclass = mdns_ntohs(data++);
uint32_t ttl = mdns_ntohl(data);
data += 2;
uint16_t length = mdns_ntohs(data++);
if (length > (data_size - offset))
return;
static char addrbuf[64];
static char entrybuf[256];
static char namebuf[256];
if (is_answer) {
offset = MDNS_POINTER_DIFF(data, buffer);
(void)sizeof(sock);
(void)sizeof(query_id);
(void)sizeof(name_length);
//(void)sizeof(0);
mdns_string_t fromaddrstr = ip_address_to_string(addrbuf, sizeof(addrbuf), saddr, addrlen);
mdns_string_t entrystr =
mdns_string_extract(buffer, data_size, &name_offset, entrybuf, sizeof(entrybuf));
if (rtype == MDNS_RECORDTYPE_PTR) {
mdns_string_t namestr = mdns_record_parse_ptr(buffer, data_size, offset, length,
namebuf, sizeof(namebuf));
if (!namestr.str || namestr.length == 0) {
return;
}
const std::string answer_name(namestr.str, namestr.length);
bool bFound = false;
for (const auto& item : prefix)
{
if (answer_name.find(item) != std::string::npos)
bFound = true;
}
if (!bFound)
{
return;
}
char ip[16] = { 0 };
sscanf(fromaddrstr.str, "%[^:]", ip);
retmachineInfos.push_back({ ip, answer_name });
}
}
}
}
std::vector<machine_info> syncDiscoveryService(const std::vector<std::string>& prefix)
{
std::vector<machine_info> retmachineInfos;
const char* hostname = "cxslice-host";
// Initialize network environment
#ifdef _WIN32
WORD versionWanted = MAKEWORD(1, 1);
WSADATA wsaData;
if (WSAStartup(versionWanted, &wsaData)) {
printf("Failed to initialize WinSock\n");
return retmachineInfos;
}
char hostname_buffer[256];
DWORD hostname_size = (DWORD)sizeof(hostname_buffer);
if (GetComputerNameA(hostname_buffer, &hostname_size))
hostname = hostname_buffer;
SetConsoleCtrlHandler(console_handler, TRUE);
#else
char hostname_buffer[256];
size_t hostname_size = sizeof(hostname_buffer);
if (gethostname(hostname_buffer, hostname_size) == 0)
hostname = hostname_buffer;
signal(SIGINT, signal_handler);
#endif
int sockets[32];
int num_sockets = open_client_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0);
if (num_sockets <= 0) {
printf("Failed to open any client sockets\n");
#ifdef _WIN32
WSACleanup();
#endif
return retmachineInfos;
}
printf("Opened %d socket%s for DNS-SD\n", num_sockets, num_sockets > 1 ? "s" : "");
printf("Sending DNS-SD discovery\n");
for (int isock = 0; isock < num_sockets; ++isock) {
if (mdns_discovery_send(sockets[isock]))
printf("Failed to send DNS-DS discovery: %s\n", strerror(errno));
}
size_t capacity = 2048;
void* buffer = malloc(capacity);
size_t recordNum = 0;
void* user_data = 0;
// This is a simple implementation that loops for 5 seconds or as long as we get replies
int res;
printf("Reading DNS-SD replies\n");
do {
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int nfds = 0;
fd_set readfs;
FD_ZERO(&readfs);
for (int isock = 0; isock < num_sockets; ++isock) {
if (sockets[isock] >= nfds)
nfds = sockets[isock] + 1;
FD_SET(sockets[isock], &readfs);
}
res = select(nfds, &readfs, 0, 0, &timeout);
if (res > 0) {
for (int isock = 0; isock < num_sockets; ++isock) {
if (FD_ISSET(sockets[isock], &readfs)) {
// records += mdns_discovery_recv(sockets[isock], buffer, capacity, query_callback,
// 0);
recvMachineInfoFromSocket(sockets[isock], buffer, capacity, prefix, retmachineInfos, isock);
recordNum++;
}
}
}
} while (res > 0);
free(buffer);
//
for (int isock = 0; isock < num_sockets; ++isock)
mdns_socket_close(sockets[isock]);
printf("Closed socket%s\n", num_sockets ? "s" : "");
#ifdef _WIN32
WSACleanup();
#endif
return std::move(retmachineInfos);
}
}

16
deps_src/mdns/cxmdns.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef _CX_MDNS_H
#define _CX_MDNS_H
#include<string>
#include<vector>
namespace cxnet
{
struct machine_info
{
std::string machineIp;
std::string answer;
};
std::vector<machine_info> syncDiscoveryService(const std::vector<std::string>& prefix);
}
#endif

1263
deps_src/mdns/mdns.c Normal file

File diff suppressed because it is too large Load Diff

1641
deps_src/mdns/mdns.h Normal file

File diff suppressed because it is too large Load Diff

29
deps_src/pybind11/LICENSE Normal file
View File

@@ -0,0 +1,29 @@
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of
external contributions to this project including patches, pull requests, etc.

View File

@@ -0,0 +1,216 @@
.. figure:: https://github.com/pybind/pybind11/raw/master/docs/pybind11-logo.png
:alt: pybind11 logo
**pybind11 (v3) — Seamless interoperability between C++ and Python**
|Latest Documentation Status| |Stable Documentation Status| |Gitter chat| |GitHub Discussions|
|CI| |Build status| |SPEC 4 — Using and Creating Nightly Wheels|
|Repology| |PyPI package| |Conda-forge| |Python Versions|
`Setuptools example <https://github.com/pybind/python_example>`_
`Scikit-build example <https://github.com/pybind/scikit_build_example>`_
`CMake example <https://github.com/pybind/cmake_example>`_
.. start
**pybind11** is a lightweight header-only library that exposes C++ types
in Python and vice versa, mainly to create Python bindings of existing
C++ code. Its goals and syntax are similar to the excellent
`Boost.Python <http://www.boost.org/doc/libs/1_58_0/libs/python/doc/>`_
library by David Abrahams: to minimize boilerplate code in traditional
extension modules by inferring type information using compile-time
introspection.
The main issue with Boost.Python—and the reason for creating such a
similar project—is Boost. Boost is an enormously large and complex suite
of utility libraries that works with almost every C++ compiler in
existence. This compatibility has its cost: arcane template tricks and
workarounds are necessary to support the oldest and buggiest of compiler
specimens. Now that C++11-compatible compilers are widely available,
this heavy machinery has become an excessively large and unnecessary
dependency.
Think of this library as a tiny self-contained version of Boost.Python
with everything stripped away that isn't relevant for binding
generation. Without comments, the core header files only require ~4K
lines of code and depend on Python (CPython 3.8+, PyPy, or GraalPy) and the C++
standard library. This compact implementation was possible thanks to some C++11
language features (specifically: tuples, lambda functions and variadic
templates). Since its creation, this library has grown beyond Boost.Python in
many ways, leading to dramatically simpler binding code in many common
situations.
Tutorial and reference documentation is provided at
`pybind11.readthedocs.io <https://pybind11.readthedocs.io/en/latest>`_.
A PDF version of the manual is available
`here <https://pybind11.readthedocs.io/_/downloads/en/latest/pdf/>`_.
And the source code is always available at
`github.com/pybind/pybind11 <https://github.com/pybind/pybind11>`_.
Core features
-------------
pybind11 can map the following core C++ features to Python:
- Functions accepting and returning custom data structures per value,
reference, or pointer
- Instance methods and static methods
- Overloaded functions
- Instance attributes and static attributes
- Arbitrary exception types
- Enumerations
- Callbacks
- Iterators and ranges
- Custom operators
- Single and multiple inheritance
- STL data structures
- Smart pointers with reference counting like ``std::shared_ptr``
- Internal references with correct reference counting
- C++ classes with virtual (and pure virtual) methods can be extended
in Python
- Integrated NumPy support (NumPy 2 requires pybind11 2.12+)
Goodies
-------
In addition to the core functionality, pybind11 provides some extra
goodies:
- CPython 3.8+, PyPy3 7.3.17+, and GraalPy 24.1+ are supported with an
implementation-agnostic interface (see older versions for older CPython
and PyPy versions).
- It is possible to bind C++11 lambda functions with captured
variables. The lambda capture data is stored inside the resulting
Python function object.
- pybind11 uses C++11 move constructors and move assignment operators
whenever possible to efficiently transfer custom data types.
- It's easy to expose the internal storage of custom data types through
Pythons' buffer protocols. This is handy e.g. for fast conversion
between C++ matrix classes like Eigen and NumPy without expensive
copy operations.
- pybind11 can automatically vectorize functions so that they are
transparently applied to all entries of one or more NumPy array
arguments.
- Python's slice-based access and assignment operations can be
supported with just a few lines of code.
- Everything is contained in just a few header files; there is no need
to link against any additional libraries.
- Binaries are generally smaller by a factor of at least 2 compared to
equivalent bindings generated by Boost.Python. A recent pybind11
conversion of PyRosetta, an enormous Boost.Python binding project,
`reported <https://graylab.jhu.edu/Sergey/2016.RosettaCon/PyRosetta-4.pdf>`_
a binary size reduction of **5.4x** and compile time reduction by
**5.8x**.
- Function signatures are precomputed at compile time (using
``constexpr``), leading to smaller binaries.
- With little extra effort, C++ types can be pickled and unpickled
similar to regular Python objects.
Supported compilers
-------------------
1. Clang/LLVM 3.3 or newer (for Apple Xcode's clang, this is 5.0.0 or
newer)
2. GCC 4.8 or newer
3. Microsoft Visual Studio 2022 or newer (2019 probably works, but was dropped in CI)
4. Intel classic C++ compiler 18 or newer (ICC 20.2 tested in CI)
5. Cygwin/GCC (previously tested on 2.5.1)
6. NVCC (CUDA 11.0 tested in CI)
7. NVIDIA PGI (20.9 tested in CI)
Supported Platforms
-------------------
* Windows, Linux, macOS, and iOS
* CPython 3.8+, Pyodide, PyPy, and GraalPy
* C++11, C++14, C++17, C++20, and C++23
About
-----
This project was created by `Wenzel
Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or
improvements to the code were contributed by
Jonas Adler,
Lori A. Burns,
Sylvain Corlay,
Eric Cousineau,
Aaron Gokaslan,
Ralf Grosse-Kunstleve,
Trent Houliston,
Axel Huebl,
@hulucc,
Yannick Jadoul,
Sergey Lyskov,
Johan Mabille,
Tomasz Miąsko,
Dean Moldovan,
Ben Pritchard,
Jason Rhinelander,
Boris Schäling,
Pim Schellart,
Henry Schreiner,
Ivan Smirnov,
Dustin Spicuzza,
Boris Staletic,
Ethan Steinberg,
Patrick Stewart,
Ivor Wanders,
and
Xiaofei Wang.
We thank Google for a generous financial contribution to the continuous
integration infrastructure used by this project.
Contributing
~~~~~~~~~~~~
See the `contributing
guide <https://github.com/pybind/pybind11/blob/master/.github/CONTRIBUTING.md>`_
for information on building and contributing to pybind11.
License
~~~~~~~
pybind11 is provided under a BSD-style license that can be found in the
`LICENSE <https://github.com/pybind/pybind11/blob/master/LICENSE>`_
file. By using, distributing, or contributing to this project, you agree
to the terms and conditions of this license.
.. |Latest Documentation Status| image:: https://readthedocs.org/projects/pybind11/badge?version=latest
:target: http://pybind11.readthedocs.org/en/latest
.. |Stable Documentation Status| image:: https://img.shields.io/badge/docs-stable-blue.svg
:target: http://pybind11.readthedocs.org/en/stable
.. |Gitter chat| image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg
:target: https://gitter.im/pybind/Lobby
.. |CI| image:: https://github.com/pybind/pybind11/workflows/CI/badge.svg
:target: https://github.com/pybind/pybind11/actions
.. |Build status| image:: https://ci.appveyor.com/api/projects/status/riaj54pn4h08xy40?svg=true
:target: https://ci.appveyor.com/project/wjakob/pybind11
.. |PyPI package| image:: https://img.shields.io/pypi/v/pybind11.svg
:target: https://pypi.org/project/pybind11/
.. |Conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pybind11.svg
:target: https://github.com/conda-forge/pybind11-feedstock
.. |Repology| image:: https://repology.org/badge/latest-versions/python:pybind11.svg
:target: https://repology.org/project/python:pybind11/versions
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/pybind11.svg
:target: https://pypi.org/project/pybind11/
.. |GitHub Discussions| image:: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github
:target: https://github.com/pybind/pybind11/discussions
.. |SPEC 4 — Using and Creating Nightly Wheels| image:: https://img.shields.io/badge/SPEC-4-green?labelColor=%23004811&color=%235CA038
:target: https://scientific-python.org/specs/spec-0004/

View File

@@ -0,0 +1,722 @@
/*
pybind11/attr.h: Infrastructure for processing custom
type and function attributes
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "detail/common.h"
#include "cast.h"
#include "trampoline_self_life_support.h"
#include <functional>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/// \addtogroup annotations
/// @{
/// Annotation for methods
struct is_method {
handle class_;
explicit is_method(const handle &c) : class_(c) {}
};
/// Annotation for setters
struct is_setter {};
/// Annotation for operators
struct is_operator {};
/// Annotation for classes that cannot be subclassed
struct is_final {};
/// Annotation for parent scope
struct scope {
handle value;
explicit scope(const handle &s) : value(s) {}
};
/// Annotation for documentation
struct doc {
const char *value;
explicit doc(const char *value) : value(value) {}
};
/// Annotation for function names
struct name {
const char *value;
explicit name(const char *value) : value(value) {}
};
/// Annotation indicating that a function is an overload associated with a given "sibling"
struct sibling {
handle value;
explicit sibling(const handle &value) : value(value.ptr()) {}
};
/// Annotation indicating that a class derives from another given type
template <typename T>
struct base {
PYBIND11_DEPRECATED(
"base<T>() was deprecated in favor of specifying 'T' as a template argument to class_")
base() = default;
};
/// Keep patient alive while nurse lives
template <size_t Nurse, size_t Patient>
struct keep_alive {};
/// Annotation indicating that a class is involved in a multiple inheritance relationship
struct multiple_inheritance {};
/// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class
struct dynamic_attr {};
/// Annotation which enables the buffer protocol for a type
struct buffer_protocol {};
/// Annotation which enables releasing the GIL before calling the C++ destructor of wrapped
/// instances (pybind/pybind11#1446).
struct release_gil_before_calling_cpp_dtor {};
/// Annotation which requests that a special metaclass is created for a type
struct metaclass {
handle value;
PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.")
metaclass() = default;
/// Override pybind11's default metaclass
explicit metaclass(handle value) : value(value) {}
};
/// Specifies a custom callback with signature `void (PyHeapTypeObject*)` that
/// may be used to customize the Python type.
///
/// The callback is invoked immediately before `PyType_Ready`.
///
/// Note: This is an advanced interface, and uses of it may require changes to
/// work with later versions of pybind11. You may wish to consult the
/// implementation of `make_new_python_type` in `detail/classes.h` to understand
/// the context in which the callback will be run.
struct custom_type_setup {
using callback = std::function<void(PyHeapTypeObject *heap_type)>;
explicit custom_type_setup(callback value) : value(std::move(value)) {}
callback value;
};
/// Annotation that marks a class as local to the module:
struct module_local {
const bool value;
constexpr explicit module_local(bool v = true) : value(v) {}
};
/// Annotation to mark enums as an arithmetic type
struct arithmetic {};
/// Mark a function for addition at the beginning of the existing overload chain instead of the end
struct prepend {};
/** \rst
A call policy which places one or more guard variables (``Ts...``) around the function call.
For example, this definition:
.. code-block:: cpp
m.def("foo", foo, py::call_guard<T>());
is equivalent to the following pseudocode:
.. code-block:: cpp
m.def("foo", [](args...) {
T scope_guard;
return foo(args...); // forwarded arguments
});
\endrst */
template <typename... Ts>
struct call_guard;
template <>
struct call_guard<> {
using type = detail::void_type;
};
template <typename T>
struct call_guard<T> {
static_assert(std::is_default_constructible<T>::value,
"The guard type must be default constructible");
using type = T;
};
template <typename T, typename... Ts>
struct call_guard<T, Ts...> {
struct type {
T guard{}; // Compose multiple guard types with left-to-right default-constructor order
typename call_guard<Ts...>::type next{};
};
};
/// @} annotations
PYBIND11_NAMESPACE_BEGIN(detail)
/* Forward declarations */
enum op_id : int;
enum op_type : int;
struct undefined_t;
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t>
struct op_;
void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
/// Internal data structure which holds metadata about a keyword argument
struct argument_record {
const char *name; ///< Argument name
const char *descr; ///< Human-readable version of the argument value
handle value; ///< Associated Python object
bool convert : 1; ///< True if the argument is allowed to convert when loading
bool none : 1; ///< True if None is allowed when loading
argument_record(const char *name, const char *descr, handle value, bool convert, bool none)
: name(name), descr(descr), value(value), convert(convert), none(none) {}
};
/// Internal data structure which holds metadata about a bound function (signature, overloads,
/// etc.)
#define PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID "v1" // PLEASE UPDATE if the struct is changed.
struct function_record {
function_record()
: is_constructor(false), is_new_style_constructor(false), is_stateless(false),
is_operator(false), is_method(false), is_setter(false), has_args(false),
has_kwargs(false), prepend(false) {}
/// Function name
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
// User-specified documentation string
char *doc = nullptr;
/// Human-readable version of the function signature
char *signature = nullptr;
/// List of registered keyword arguments
std::vector<argument_record> args;
/// Pointer to lambda function which converts arguments and performs the actual call
handle (*impl)(function_call &) = nullptr;
/// Storage for the wrapped function pointer and captured data, if any
void *data[3] = {};
/// Pointer to custom destructor for 'data' (if needed)
void (*free_data)(function_record *ptr) = nullptr;
/// Return value policy associated with this function
return_value_policy policy = return_value_policy::automatic;
/// True if name == '__init__'
bool is_constructor : 1;
/// True if this is a new-style `__init__` defined in `detail/init.h`
bool is_new_style_constructor : 1;
/// True if this is a stateless function pointer
bool is_stateless : 1;
/// True if this is an operator (__add__), etc.
bool is_operator : 1;
/// True if this is a method
bool is_method : 1;
/// True if this is a setter
bool is_setter : 1;
/// True if the function has a '*args' argument
bool has_args : 1;
/// True if the function has a '**kwargs' argument
bool has_kwargs : 1;
/// True if this function is to be inserted at the beginning of the overload resolution chain
bool prepend : 1;
/// Number of arguments (including py::args and/or py::kwargs, if present)
std::uint16_t nargs;
/// Number of leading positional arguments, which are terminated by a py::args or py::kwargs
/// argument or by a py::kw_only annotation.
std::uint16_t nargs_pos = 0;
/// Number of leading arguments (counted in `nargs`) that are positional-only
std::uint16_t nargs_pos_only = 0;
/// Python method object
PyMethodDef *def = nullptr;
/// Python handle to the parent scope (a class or a module)
handle scope;
/// Python handle to the sibling function representing an overload chain
handle sibling;
/// Pointer to next overload
function_record *next = nullptr;
};
// The main purpose of this macro is to make it easy to pin-point the critically related code
// sections.
#define PYBIND11_ENSURE_PRECONDITION_FOR_FUNCTIONAL_H_PERFORMANCE_OPTIMIZATIONS(...) \
static_assert( \
__VA_ARGS__, \
"Violation of precondition for pybind11/functional.h performance optimizations!")
/// Special data structure which (temporarily) holds metadata about a bound class
struct type_record {
PYBIND11_NOINLINE type_record()
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false),
module_local(false), is_final(false), release_gil_before_calling_cpp_dtor(false) {}
/// Handle to the parent scope
handle scope;
/// Name of the class
const char *name = nullptr;
// Pointer to RTTI type_info data structure
const std::type_info *type = nullptr;
/// How large is the underlying C++ type?
size_t type_size = 0;
/// What is the alignment of the underlying C++ type?
size_t type_align = 0;
/// How large is the type's holder?
size_t holder_size = 0;
/// The global operator new can be overridden with a class-specific variant
void *(*operator_new)(size_t) = nullptr;
/// Function pointer to class_<..>::init_instance
void (*init_instance)(instance *, const void *) = nullptr;
/// Function pointer to class_<..>::dealloc
void (*dealloc)(detail::value_and_holder &) = nullptr;
/// Function pointer for casting alias class (aka trampoline) pointer to
/// trampoline_self_life_support pointer. Sidesteps cross-DSO RTTI issues
/// on platforms like macOS (see PR #5728 for details).
get_trampoline_self_life_support_fn get_trampoline_self_life_support
= [](void *) -> trampoline_self_life_support * { return nullptr; };
/// List of base classes of the newly created type
list bases;
/// Optional docstring
const char *doc = nullptr;
/// Custom metaclass (optional)
handle metaclass;
/// Custom type setup.
custom_type_setup::callback custom_type_setup_callback;
/// Multiple inheritance marker
bool multiple_inheritance : 1;
/// Does the class manage a __dict__?
bool dynamic_attr : 1;
/// Does the class implement the buffer protocol?
bool buffer_protocol : 1;
/// Is the class definition local to the module shared object?
bool module_local : 1;
/// Is the class inheritable from python classes?
bool is_final : 1;
/// Solves pybind/pybind11#1446
bool release_gil_before_calling_cpp_dtor : 1;
holder_enum_t holder_enum_v = holder_enum_t::undefined;
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) {
auto *base_info = detail::get_type_info(base, false);
if (!base_info) {
std::string tname(base.name());
detail::clean_type_id(tname);
pybind11_fail("generic_type: type \"" + std::string(name)
+ "\" referenced unknown base type \"" + tname + "\"");
}
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks.
bool this_has_unique_ptr_holder = (holder_enum_v == holder_enum_t::std_unique_ptr);
bool base_has_unique_ptr_holder
= (base_info->holder_enum_v == holder_enum_t::std_unique_ptr);
if (this_has_unique_ptr_holder != base_has_unique_ptr_holder) {
std::string tname(base.name());
detail::clean_type_id(tname);
pybind11_fail("generic_type: type \"" + std::string(name) + "\" "
+ (this_has_unique_ptr_holder ? "does not have" : "has")
+ " a non-default holder type while its base \"" + tname + "\" "
+ (base_has_unique_ptr_holder ? "does not" : "does"));
}
bases.append((PyObject *) base_info->type);
#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
dynamic_attr |= base_info->type->tp_dictoffset != 0;
#else
dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0;
#endif
if (caster) {
base_info->implicit_casts.emplace_back(type, caster);
}
}
};
inline function_call::function_call(const function_record &f, handle p) : func(f), parent(p) {
args.reserve(f.nargs);
args_convert.reserve(f.nargs);
}
/// Tag for a new-style `__init__` defined in `detail/init.h`
struct is_new_style_constructor {};
/**
* Partial template specializations to process custom attributes provided to
* cpp_function_ and class_. These are either used to initialize the respective
* fields in the type_record and function_record data structures or executed at
* runtime to deal with custom call policies (e.g. keep_alive).
*/
template <typename T, typename SFINAE = void>
struct process_attribute;
template <typename T>
struct process_attribute_default {
/// Default implementation: do nothing
static void init(const T &, function_record *) {}
static void init(const T &, type_record *) {}
static void precall(function_call &) {}
static void postcall(function_call &, handle) {}
};
/// Process an attribute specifying the function's name
template <>
struct process_attribute<name> : process_attribute_default<name> {
static void init(const name &n, function_record *r) { r->name = const_cast<char *>(n.value); }
};
/// Process an attribute specifying the function's docstring
template <>
struct process_attribute<doc> : process_attribute_default<doc> {
static void init(const doc &n, function_record *r) { r->doc = const_cast<char *>(n.value); }
};
/// Process an attribute specifying the function's docstring (provided as a C-style string)
template <>
struct process_attribute<const char *> : process_attribute_default<const char *> {
static void init(const char *d, function_record *r) { r->doc = const_cast<char *>(d); }
static void init(const char *d, type_record *r) { r->doc = d; }
};
template <>
struct process_attribute<char *> : process_attribute<const char *> {};
/// Process an attribute indicating the function's return value policy
template <>
struct process_attribute<return_value_policy> : process_attribute_default<return_value_policy> {
static void init(const return_value_policy &p, function_record *r) { r->policy = p; }
};
/// Process an attribute which indicates that this is an overloaded function associated with a
/// given sibling
template <>
struct process_attribute<sibling> : process_attribute_default<sibling> {
static void init(const sibling &s, function_record *r) { r->sibling = s.value; }
};
/// Process an attribute which indicates that this function is a method
template <>
struct process_attribute<is_method> : process_attribute_default<is_method> {
static void init(const is_method &s, function_record *r) {
r->is_method = true;
r->scope = s.class_;
}
};
/// Process an attribute which indicates that this function is a setter
template <>
struct process_attribute<is_setter> : process_attribute_default<is_setter> {
static void init(const is_setter &, function_record *r) { r->is_setter = true; }
};
/// Process an attribute which indicates the parent scope of a method
template <>
struct process_attribute<scope> : process_attribute_default<scope> {
static void init(const scope &s, function_record *r) { r->scope = s.value; }
};
/// Process an attribute which indicates that this function is an operator
template <>
struct process_attribute<is_operator> : process_attribute_default<is_operator> {
static void init(const is_operator &, function_record *r) { r->is_operator = true; }
};
template <>
struct process_attribute<is_new_style_constructor>
: process_attribute_default<is_new_style_constructor> {
static void init(const is_new_style_constructor &, function_record *r) {
r->is_new_style_constructor = true;
}
};
inline void check_kw_only_arg(const arg &a, function_record *r) {
if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0')) {
pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or "
"args() argument");
}
}
inline void append_self_arg_if_needed(function_record *r) {
if (r->is_method && r->args.empty()) {
r->args.emplace_back("self", nullptr, handle(), /*convert=*/true, /*none=*/false);
}
}
/// Process a keyword argument attribute (*without* a default value)
template <>
struct process_attribute<arg> : process_attribute_default<arg> {
static void init(const arg &a, function_record *r) {
append_self_arg_if_needed(r);
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none);
check_kw_only_arg(a, r);
}
};
/// Process a keyword argument attribute (*with* a default value)
template <>
struct process_attribute<arg_v> : process_attribute_default<arg_v> {
static void init(const arg_v &a, function_record *r) {
if (r->is_method && r->args.empty()) {
r->args.emplace_back(
"self", /*descr=*/nullptr, /*parent=*/handle(), /*convert=*/true, /*none=*/false);
}
if (!a.value) {
#if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
std::string descr("'");
if (a.name) {
descr += std::string(a.name) + ": ";
}
descr += a.type + "'";
if (r->is_method) {
if (r->name) {
descr += " in method '" + (std::string) str(r->scope) + "."
+ (std::string) r->name + "'";
} else {
descr += " in method of '" + (std::string) str(r->scope) + "'";
}
} else if (r->name) {
descr += " in function '" + (std::string) r->name + "'";
}
pybind11_fail("arg(): could not convert default argument " + descr
+ " into a Python object (type not registered yet?)");
#else
pybind11_fail("arg(): could not convert default argument "
"into a Python object (type not registered yet?). "
"#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for "
"more information.");
#endif
}
r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none);
check_kw_only_arg(a, r);
}
};
/// Process a keyword-only-arguments-follow pseudo argument
template <>
struct process_attribute<kw_only> : process_attribute_default<kw_only> {
static void init(const kw_only &, function_record *r) {
append_self_arg_if_needed(r);
if (r->has_args && r->nargs_pos != static_cast<std::uint16_t>(r->args.size())) {
pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative "
"argument location (or omit kw_only() entirely)");
}
r->nargs_pos = static_cast<std::uint16_t>(r->args.size());
}
};
/// Process a positional-only-argument maker
template <>
struct process_attribute<pos_only> : process_attribute_default<pos_only> {
static void init(const pos_only &, function_record *r) {
append_self_arg_if_needed(r);
r->nargs_pos_only = static_cast<std::uint16_t>(r->args.size());
if (r->nargs_pos_only > r->nargs_pos) {
pybind11_fail("pos_only(): cannot follow a py::args() argument");
}
// It also can't follow a kw_only, but a static_assert in pybind11.h checks that
}
};
/// Process a parent class attribute. Single inheritance only (class_ itself already guarantees
/// that)
template <typename T>
struct process_attribute<T, enable_if_t<is_pyobject<T>::value>>
: process_attribute_default<handle> {
static void init(const handle &h, type_record *r) { r->bases.append(h); }
};
/// Process a parent class attribute (deprecated, does not support multiple inheritance)
template <typename T>
struct process_attribute<base<T>> : process_attribute_default<base<T>> {
static void init(const base<T> &, type_record *r) { r->add_base(typeid(T), nullptr); }
};
/// Process a multiple inheritance attribute
template <>
struct process_attribute<multiple_inheritance> : process_attribute_default<multiple_inheritance> {
static void init(const multiple_inheritance &, type_record *r) {
r->multiple_inheritance = true;
}
};
template <>
struct process_attribute<dynamic_attr> : process_attribute_default<dynamic_attr> {
static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; }
};
template <>
struct process_attribute<custom_type_setup> {
static void init(const custom_type_setup &value, type_record *r) {
r->custom_type_setup_callback = value.value;
}
};
template <>
struct process_attribute<is_final> : process_attribute_default<is_final> {
static void init(const is_final &, type_record *r) { r->is_final = true; }
};
template <>
struct process_attribute<buffer_protocol> : process_attribute_default<buffer_protocol> {
static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; }
};
template <>
struct process_attribute<metaclass> : process_attribute_default<metaclass> {
static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; }
};
template <>
struct process_attribute<module_local> : process_attribute_default<module_local> {
static void init(const module_local &l, type_record *r) { r->module_local = l.value; }
};
template <>
struct process_attribute<release_gil_before_calling_cpp_dtor>
: process_attribute_default<release_gil_before_calling_cpp_dtor> {
static void init(const release_gil_before_calling_cpp_dtor &, type_record *r) {
r->release_gil_before_calling_cpp_dtor = true;
}
};
/// Process a 'prepend' attribute, putting this at the beginning of the overload chain
template <>
struct process_attribute<prepend> : process_attribute_default<prepend> {
static void init(const prepend &, function_record *r) { r->prepend = true; }
};
/// Process an 'arithmetic' attribute for enums (does nothing here)
template <>
struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};
template <typename... Ts>
struct process_attribute<call_guard<Ts...>> : process_attribute_default<call_guard<Ts...>> {};
/**
* Process a keep_alive call policy -- invokes keep_alive_impl during the
* pre-call handler if both Nurse, Patient != 0 and use the post-call handler
* otherwise
*/
template <size_t Nurse, size_t Patient>
struct process_attribute<keep_alive<Nurse, Patient>>
: public process_attribute_default<keep_alive<Nurse, Patient>> {
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
static void precall(function_call &call) {
keep_alive_impl(Nurse, Patient, call, handle());
}
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
static void postcall(function_call &, handle) {}
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
static void precall(function_call &) {}
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
static void postcall(function_call &call, handle ret) {
keep_alive_impl(Nurse, Patient, call, ret);
}
};
/// Recursively iterate over variadic template arguments
template <typename... Args>
struct process_attributes {
static void init(const Args &...args, function_record *r) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r);
PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r);
using expander = int[];
(void) expander{
0, ((void) process_attribute<typename std::decay<Args>::type>::init(args, r), 0)...};
}
static void init(const Args &...args, type_record *r) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r);
PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r);
using expander = int[];
(void) expander{0,
(process_attribute<typename std::decay<Args>::type>::init(args, r), 0)...};
}
static void precall(function_call &call) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call);
using expander = int[];
(void) expander{0,
(process_attribute<typename std::decay<Args>::type>::precall(call), 0)...};
}
static void postcall(function_call &call, handle fn_ret) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call, fn_ret);
PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(fn_ret);
using expander = int[];
(void) expander{
0, (process_attribute<typename std::decay<Args>::type>::postcall(call, fn_ret), 0)...};
}
};
template <typename T>
using is_call_guard = is_instantiation<call_guard, T>;
/// Extract the ``type`` from the first `call_guard` in `Extras...` (or `void_type` if none found)
template <typename... Extra>
using extract_guard_t = typename exactly_one_t<is_call_guard, call_guard<>, Extra...>::type;
/// Check the number of named arguments at compile time
template <typename... Extra,
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs);
return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs;
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,208 @@
/*
pybind11/buffer_info.h: Python buffer object interface
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "detail/common.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
// Default, C-style strides
inline std::vector<ssize_t> c_strides(const std::vector<ssize_t> &shape, ssize_t itemsize) {
auto ndim = shape.size();
std::vector<ssize_t> strides(ndim, itemsize);
if (ndim > 0) {
for (size_t i = ndim - 1; i > 0; --i) {
strides[i - 1] = strides[i] * shape[i];
}
}
return strides;
}
// F-style strides; default when constructing an array_t with `ExtraFlags & f_style`
inline std::vector<ssize_t> f_strides(const std::vector<ssize_t> &shape, ssize_t itemsize) {
auto ndim = shape.size();
std::vector<ssize_t> strides(ndim, itemsize);
for (size_t i = 1; i < ndim; ++i) {
strides[i] = strides[i - 1] * shape[i - 1];
}
return strides;
}
template <typename T, typename SFINAE = void>
struct compare_buffer_info;
PYBIND11_NAMESPACE_END(detail)
/// Information record describing a Python buffer object
struct buffer_info {
void *ptr = nullptr; // Pointer to the underlying storage
ssize_t itemsize = 0; // Size of individual items in bytes
ssize_t size = 0; // Total number of entries
std::string format; // For homogeneous buffers, this should be set to
// format_descriptor<T>::format()
ssize_t ndim = 0; // Number of dimensions
std::vector<ssize_t> shape; // Shape of the tensor (1 entry per dimension)
std::vector<ssize_t> strides; // Number of bytes between adjacent entries
// (for each per dimension)
bool readonly = false; // flag to indicate if the underlying storage may be written to
buffer_info() = default;
buffer_info(void *ptr,
ssize_t itemsize,
const std::string &format,
ssize_t ndim,
detail::any_container<ssize_t> shape_in,
detail::any_container<ssize_t> strides_in,
bool readonly = false)
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) {
pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
}
for (size_t i = 0; i < (size_t) ndim; ++i) {
size *= shape[i];
}
}
template <typename T>
buffer_info(T *ptr,
detail::any_container<ssize_t> shape_in,
detail::any_container<ssize_t> strides_in,
bool readonly = false)
: buffer_info(private_ctr_tag(),
ptr,
sizeof(T),
format_descriptor<T>::format(),
static_cast<ssize_t>(shape_in->size()),
std::move(shape_in),
std::move(strides_in),
readonly) {}
buffer_info(void *ptr,
ssize_t itemsize,
const std::string &format,
ssize_t size,
bool readonly = false)
: buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) {}
template <typename T>
buffer_info(T *ptr, ssize_t size, bool readonly = false)
: buffer_info(ptr, sizeof(T), format_descriptor<T>::format(), size, readonly) {}
template <typename T>
buffer_info(const T *ptr, ssize_t size, bool readonly = true)
: buffer_info(
const_cast<T *>(ptr), sizeof(T), format_descriptor<T>::format(), size, readonly) {}
explicit buffer_info(Py_buffer *view, bool ownview = true)
: buffer_info(
view->buf,
view->itemsize,
view->format,
view->ndim,
{view->shape, view->shape + view->ndim},
/* Though buffer::request() requests PyBUF_STRIDES, ctypes objects
* ignore this flag and return a view with NULL strides.
* When strides are NULL, build them manually. */
view->strides
? std::vector<ssize_t>(view->strides, view->strides + view->ndim)
: detail::c_strides({view->shape, view->shape + view->ndim}, view->itemsize),
(view->readonly != 0)) {
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
this->m_view = view;
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
this->ownview = ownview;
}
buffer_info(const buffer_info &) = delete;
buffer_info &operator=(const buffer_info &) = delete;
buffer_info(buffer_info &&other) noexcept { (*this) = std::move(other); }
buffer_info &operator=(buffer_info &&rhs) noexcept {
ptr = rhs.ptr;
itemsize = rhs.itemsize;
size = rhs.size;
format = std::move(rhs.format);
ndim = rhs.ndim;
shape = std::move(rhs.shape);
strides = std::move(rhs.strides);
std::swap(m_view, rhs.m_view);
std::swap(ownview, rhs.ownview);
readonly = rhs.readonly;
return *this;
}
~buffer_info() {
if (m_view && ownview) {
PyBuffer_Release(m_view);
delete m_view;
}
}
Py_buffer *view() const { return m_view; }
Py_buffer *&view() { return m_view; }
/* True if the buffer item type is equivalent to `T`. */
// To define "equivalent" by example:
// `buffer_info::item_type_is_equivalent_to<int>(b)` and
// `buffer_info::item_type_is_equivalent_to<long>(b)` may both be true
// on some platforms, but `int` and `unsigned` will never be equivalent.
// For the ground truth, please inspect `detail::compare_buffer_info<>`.
template <typename T>
bool item_type_is_equivalent_to() const {
return detail::compare_buffer_info<T>::compare(*this);
}
private:
struct private_ctr_tag {};
buffer_info(private_ctr_tag,
void *ptr,
ssize_t itemsize,
const std::string &format,
ssize_t ndim,
detail::any_container<ssize_t> &&shape_in,
detail::any_container<ssize_t> &&strides_in,
bool readonly)
: buffer_info(
ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) {}
Py_buffer *m_view = nullptr;
bool ownview = false;
};
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T, typename SFINAE>
struct compare_buffer_info {
static bool compare(const buffer_info &b) {
// NOLINTNEXTLINE(bugprone-sizeof-expression) Needed for `PyObject *`
return b.format == format_descriptor<T>::format() && b.itemsize == (ssize_t) sizeof(T);
}
};
template <typename T>
struct compare_buffer_info<T, detail::enable_if_t<std::is_integral<T>::value>> {
static bool compare(const buffer_info &b) {
return (size_t) b.itemsize == sizeof(T)
&& (b.format == format_descriptor<T>::value
|| ((sizeof(T) == sizeof(long))
&& b.format == (std::is_unsigned<T>::value ? "L" : "l"))
|| ((sizeof(T) == sizeof(size_t))
&& b.format == (std::is_unsigned<T>::value ? "N" : "n")));
}
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,228 @@
/*
pybind11/chrono.h: Transparent conversion between std::chrono and python's datetime
Copyright (c) 2016 Trent Houliston <trent@houliston.me> and
Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include <chrono>
#include <cmath>
#include <ctime>
#include <datetime.h>
#include <mutex>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename type>
class duration_caster {
public:
using rep = typename type::rep;
using period = typename type::period;
// signed 25 bits required by the standard.
using days = std::chrono::duration<int_least32_t, std::ratio<86400>>;
bool load(handle src, bool) {
using namespace std::chrono;
// Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
if (!src) {
return false;
}
// If invoked with datetime.delta object
if (PyDelta_Check(src.ptr())) {
value = type(duration_cast<duration<rep, period>>(
days(PyDateTime_DELTA_GET_DAYS(src.ptr()))
+ seconds(PyDateTime_DELTA_GET_SECONDS(src.ptr()))
+ microseconds(PyDateTime_DELTA_GET_MICROSECONDS(src.ptr()))));
return true;
}
// If invoked with a float we assume it is seconds and convert
if (PyFloat_Check(src.ptr())) {
value = type(duration_cast<duration<rep, period>>(
duration<double>(PyFloat_AsDouble(src.ptr()))));
return true;
}
return false;
}
// If this is a duration just return it back
static const std::chrono::duration<rep, period> &
get_duration(const std::chrono::duration<rep, period> &src) {
return src;
}
static const std::chrono::duration<rep, period> &
get_duration(const std::chrono::duration<rep, period> &&)
= delete;
// If this is a time_point get the time_since_epoch
template <typename Clock>
static std::chrono::duration<rep, period>
get_duration(const std::chrono::time_point<Clock, std::chrono::duration<rep, period>> &src) {
return src.time_since_epoch();
}
static handle cast(const type &src, return_value_policy /* policy */, handle /* parent */) {
using namespace std::chrono;
// Use overloaded function to get our duration from our source
// Works out if it is a duration or time_point and get the duration
auto d = get_duration(src);
// Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
// Declare these special duration types so the conversions happen with the correct
// primitive types (int)
using dd_t = duration<int, std::ratio<86400>>;
using ss_t = duration<int, std::ratio<1>>;
using us_t = duration<int, std::micro>;
auto dd = duration_cast<dd_t>(d);
auto subd = d - dd;
auto ss = duration_cast<ss_t>(subd);
auto us = duration_cast<us_t>(subd - ss);
return PyDelta_FromDSU(dd.count(), ss.count(), us.count());
}
PYBIND11_TYPE_CASTER(type, const_name("datetime.timedelta"));
};
inline std::tm *localtime_thread_safe(const std::time_t *time, std::tm *buf) {
#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || defined(_MSC_VER)
if (localtime_s(buf, time))
return nullptr;
return buf;
#else
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
std::tm *tm_ptr = std::localtime(time);
if (tm_ptr != nullptr) {
*buf = *tm_ptr;
}
return tm_ptr;
#endif
}
// This is for casting times on the system clock into datetime.datetime instances
template <typename Duration>
class type_caster<std::chrono::time_point<std::chrono::system_clock, Duration>> {
public:
using type = std::chrono::time_point<std::chrono::system_clock, Duration>;
bool load(handle src, bool) {
using namespace std::chrono;
// Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
if (!src) {
return false;
}
std::tm cal;
microseconds msecs;
if (PyDateTime_Check(src.ptr())) {
cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr());
cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr());
cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr());
cal.tm_mday = PyDateTime_GET_DAY(src.ptr());
cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1;
cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900;
cal.tm_isdst = -1;
msecs = microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr()));
} else if (PyDate_Check(src.ptr())) {
cal.tm_sec = 0;
cal.tm_min = 0;
cal.tm_hour = 0;
cal.tm_mday = PyDateTime_GET_DAY(src.ptr());
cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1;
cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900;
cal.tm_isdst = -1;
msecs = microseconds(0);
} else if (PyTime_Check(src.ptr())) {
cal.tm_sec = PyDateTime_TIME_GET_SECOND(src.ptr());
cal.tm_min = PyDateTime_TIME_GET_MINUTE(src.ptr());
cal.tm_hour = PyDateTime_TIME_GET_HOUR(src.ptr());
cal.tm_mday = 1; // This date (day, month, year) = (1, 0, 70)
cal.tm_mon = 0; // represents 1-Jan-1970, which is the first
cal.tm_year = 70; // earliest available date for Python's datetime
cal.tm_isdst = -1;
msecs = microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr()));
} else {
return false;
}
value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs);
return true;
}
static handle cast(const std::chrono::time_point<std::chrono::system_clock, Duration> &src,
return_value_policy /* policy */,
handle /* parent */) {
using namespace std::chrono;
// Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
// Get out microseconds, and make sure they are positive, to avoid bug in eastern
// hemisphere time zones (cfr. https://github.com/pybind/pybind11/issues/2417)
using us_t = duration<int, std::micro>;
auto us = duration_cast<us_t>(src.time_since_epoch() % seconds(1));
if (us.count() < 0) {
us += duration_cast<us_t>(seconds(1));
}
// Subtract microseconds BEFORE `system_clock::to_time_t`, because:
// > If std::time_t has lower precision, it is implementation-defined whether the value is
// rounded or truncated. (https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t)
std::time_t tt
= system_clock::to_time_t(time_point_cast<system_clock::duration>(src - us));
std::tm localtime;
std::tm *localtime_ptr = localtime_thread_safe(&tt, &localtime);
if (!localtime_ptr) {
throw cast_error("Unable to represent system_clock in local time");
}
return PyDateTime_FromDateAndTime(localtime.tm_year + 1900,
localtime.tm_mon + 1,
localtime.tm_mday,
localtime.tm_hour,
localtime.tm_min,
localtime.tm_sec,
us.count());
}
PYBIND11_TYPE_CASTER(type, const_name("datetime.datetime"));
};
// Other clocks that are not the system clock are not measured as datetime.datetime objects
// since they are not measured on calendar time. So instead we just make them timedeltas
// Or if they have passed us a time as a float we convert that
template <typename Clock, typename Duration>
class type_caster<std::chrono::time_point<Clock, Duration>>
: public duration_caster<std::chrono::time_point<Clock, Duration>> {};
template <typename Rep, typename Period>
class type_caster<std::chrono::duration<Rep, Period>>
: public duration_caster<std::chrono::duration<Rep, Period>> {};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,2 @@
#include "detail/common.h"
#warning "Including 'common.h' is deprecated. It will be removed in v3.0. Use 'pybind11.h'."

View File

@@ -0,0 +1,74 @@
/*
pybind11/complex.h: Complex number support
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include <complex>
/// glibc defines I as a macro which breaks things, e.g., boost template names
#ifdef I
# undef I
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
template <typename T>
struct format_descriptor<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> {
static constexpr const char c = format_descriptor<T>::c;
static constexpr const char value[3] = {'Z', c, '\0'};
static std::string format() { return std::string(value); }
};
#ifndef PYBIND11_CPP17
template <typename T>
constexpr const char
format_descriptor<std::complex<T>,
detail::enable_if_t<std::is_floating_point<T>::value>>::value[3];
#endif
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T>
struct is_fmt_numeric<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> {
static constexpr bool value = true;
static constexpr int index = is_fmt_numeric<T>::index + 3;
};
template <typename T>
class type_caster<std::complex<T>> {
public:
bool load(handle src, bool convert) {
if (!src) {
return false;
}
if (!convert && !PyComplex_Check(src.ptr())) {
return false;
}
Py_complex result = PyComplex_AsCComplex(src.ptr());
if (result.real == -1.0 && PyErr_Occurred()) {
PyErr_Clear();
return false;
}
value = std::complex<T>((T) result.real, (T) result.imag);
return true;
}
static handle
cast(const std::complex<T> &src, return_value_policy /* policy */, handle /* parent */) {
return PyComplex_FromDoubles((double) src.real(), (double) src.imag());
}
PYBIND11_TYPE_CASTER(std::complex<T>, const_name("complex"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,15 @@
NOTE
----
The C++ code here
** only depends on <Python.h> **
and nothing else.
DO NOT ADD CODE WITH OTHER EXTERNAL DEPENDENCIES TO THIS DIRECTORY.
Read on:
pybind11_conduit_v1.h — Type-safe interoperability between different
independent Python/C++ bindings systems.

View File

@@ -0,0 +1,116 @@
// Copyright (c) 2024 The pybind Community.
/* The pybind11_conduit_v1 feature enables type-safe interoperability between
* different independent Python/C++ bindings systems,
* including pybind11 versions with different PYBIND11_INTERNALS_VERSION's.
* NOTE: The conduit feature
only covers from-Python-to-C++ conversions, it
does not cover from-C++-to-Python conversions.
(For the latter, a different feature would have to be added.)
The naming of the feature is a bit misleading:
* The feature is in no way tied to pybind11 internals.
* It just happens to originate from pybind11 and currently still lives there.
* The only external dependency is <Python.h>.
The implementation is a VERY light-weight dependency. It is designed to be
compatible with any ISO C++11 (or higher) compiler, and does NOT require
C++ Exception Handling to be enabled.
Please see https://github.com/pybind/pybind11/pull/5296 for more background.
The implementation involves a
def _pybind11_conduit_v1_(
self,
pybind11_platform_abi_id: bytes,
cpp_type_info_capsule: capsule,
pointer_kind: bytes) -> capsule
method that is meant to be added to Python objects wrapping C++ objects
(e.g. pybind11::class_-wrapped types).
The design of the _pybind11_conduit_v1_ feature provides two layers of
protection against C++ ABI mismatches:
* The first and most important layer is that the pybind11_platform_abi_id's
must match between extensions. — This will never be perfect, but is the same
pragmatic approach used in pybind11 since 2017
(https://github.com/pybind/pybind11/commit/96997a4b9d4ec3d389a570604394af5d5eee2557,
PYBIND11_INTERNALS_ID).
* The second layer is that the typeid(std::type_info).name()'s must match
between extensions.
The implementation below (which is shorter than this comment!), serves as a
battle-tested specification. The main API is this one function:
auto *cpp_pointer = pybind11_conduit_v1::get_type_pointer_ephemeral<YourType>(py_obj);
It is meant to be a minimalistic reference implementation, intentionally
without comprehensive error reporting. It is expected that major bindings
systems will roll their own, compatible implementations, potentially with
system-specific error reporting. The essential specifications all bindings
systems need to agree on are merely:
* PYBIND11_PLATFORM_ABI_ID (const char* literal).
* The cpp_type_info capsule (see below: a void *ptr and a const char *name).
* The cpp_conduit capsule (see below: a void *ptr and a const char *name).
* "raw_pointer_ephemeral" means: the lifetime of the pointer is the lifetime
of the py_obj.
*/
// THIS MUST STAY AT THE TOP!
#include "pybind11_platform_abi_id.h"
#include <Python.h>
#include <typeinfo>
namespace pybind11_conduit_v1 {
inline void *get_raw_pointer_ephemeral(PyObject *py_obj, const std::type_info *cpp_type_info) {
PyObject *cpp_type_info_capsule
= PyCapsule_New(const_cast<void *>(static_cast<const void *>(cpp_type_info)),
typeid(std::type_info).name(),
nullptr);
if (cpp_type_info_capsule == nullptr) {
return nullptr;
}
PyObject *cpp_conduit = PyObject_CallMethod(py_obj,
"_pybind11_conduit_v1_",
"yOy",
PYBIND11_PLATFORM_ABI_ID,
cpp_type_info_capsule,
"raw_pointer_ephemeral");
Py_DECREF(cpp_type_info_capsule);
if (cpp_conduit == nullptr) {
return nullptr;
}
void *raw_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name());
Py_DECREF(cpp_conduit);
if (PyErr_Occurred()) {
return nullptr;
}
return raw_ptr;
}
template <typename T>
T *get_type_pointer_ephemeral(PyObject *py_obj) {
void *raw_ptr = get_raw_pointer_ephemeral(py_obj, &typeid(T));
if (raw_ptr == nullptr) {
return nullptr;
}
return static_cast<T *>(raw_ptr);
}
} // namespace pybind11_conduit_v1

View File

@@ -0,0 +1,87 @@
#pragma once
// Copyright (c) 2024 The pybind Community.
// To maximize reusability:
// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING.
#include "wrap_include_python_h.h"
// Implementation details. DO NOT USE ELSEWHERE. (Unfortunately we cannot #undef them.)
// This is duplicated here to maximize portability.
#define PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) #x
#define PYBIND11_PLATFORM_ABI_ID_TOSTRING(x) PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x)
#ifdef PYBIND11_COMPILER_TYPE
// // To maintain backward compatibility (see PR #5439).
# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE ""
#else
# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "_"
# if defined(__MINGW32__)
# define PYBIND11_COMPILER_TYPE "mingw"
# elif defined(__CYGWIN__)
# define PYBIND11_COMPILER_TYPE "gcc_cygwin"
# elif defined(_MSC_VER)
# define PYBIND11_COMPILER_TYPE "msvc"
# elif defined(__clang__) || defined(__GNUC__)
# define PYBIND11_COMPILER_TYPE "system" // Assumed compatible with system compiler.
# else
# error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE."
# endif
#endif
// PR #5439 made this macro obsolete. However, there are many manipulations of this macro in the
// wild. Therefore, to maintain backward compatibility, it is kept around.
#ifndef PYBIND11_STDLIB
# define PYBIND11_STDLIB ""
#endif
#ifndef PYBIND11_BUILD_ABI
# if defined(_MSC_VER) // See PR #4953.
# if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd.
# if (_MSC_VER) / 100 == 19
# define PYBIND11_BUILD_ABI "_md_mscver19"
# else
# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE."
# endif
# elif defined(_MT) // Corresponding to CL command line options /MT or /MTd.
# define PYBIND11_BUILD_ABI "_mt_mscver" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_MSC_VER)
# else
# if (_MSC_VER) / 100 == 19
# define PYBIND11_BUILD_ABI "_none_mscver19"
# else
# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE."
# endif
# endif
# elif defined(_LIBCPP_ABI_VERSION) // https://libcxx.llvm.org/DesignDocs/ABIVersioning.html
# define PYBIND11_BUILD_ABI \
"_libcpp_abi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_LIBCPP_ABI_VERSION)
# elif defined(_GLIBCXX_USE_CXX11_ABI) // See PR #5439.
# if defined(__NVCOMPILER)
// // Assume that NVHPC is in the 1xxx ABI family.
// // THIS ASSUMPTION IS NOT FUTURE PROOF but apparently the best we can do.
// // Please let us know if there is a way to validate the assumption here.
# elif !defined(__GXX_ABI_VERSION)
# error \
"Unknown platform or compiler (_GLIBCXX_USE_CXX11_ABI): PLEASE REVISE THIS CODE."
# endif
# if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION < 1002 || __GXX_ABI_VERSION >= 2000
# error "Unknown platform or compiler (__GXX_ABI_VERSION): PLEASE REVISE THIS CODE."
# endif
# define PYBIND11_BUILD_ABI \
"_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_" PYBIND11_PLATFORM_ABI_ID_TOSTRING( \
_GLIBCXX_USE_CXX11_ABI)
# else
# error "Unknown platform or compiler: PLEASE REVISE THIS CODE."
# endif
#endif
// On MSVC, debug and release builds are not ABI-compatible!
#if defined(_MSC_VER) && defined(_DEBUG)
# define PYBIND11_BUILD_TYPE "_debug"
#else
# define PYBIND11_BUILD_TYPE ""
#endif
#define PYBIND11_PLATFORM_ABI_ID \
PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE

View File

@@ -0,0 +1,72 @@
#pragma once
// Copyright (c) 2024 The pybind Community.
// STRONG REQUIREMENT:
// This header is a wrapper around `#include <Python.h>`, therefore it
// MUST BE INCLUDED BEFORE ANY STANDARD HEADERS are included.
// See also:
// https://docs.python.org/3/c-api/intro.html#include-files
// Quoting from there:
// Note: Since Python may define some pre-processor definitions which affect
// the standard headers on some systems, you must include Python.h before
// any standard headers are included.
// To maximize reusability:
// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING.
// Disable linking to pythonX_d.lib on Windows in debug mode.
#if defined(_MSC_VER) && defined(_DEBUG) && !defined(Py_DEBUG)
// Workaround for a VS 2022 issue.
// See https://github.com/pybind/pybind11/pull/3497 for full context.
// NOTE: This workaround knowingly violates the Python.h include order
// requirement (see above).
# include <yvals.h>
# if _MSVC_STL_VERSION >= 143
# include <crtdefs.h>
# endif
# define PYBIND11_DEBUG_MARKER
# undef _DEBUG
#endif
// Don't let Python.h #define (v)snprintf as macro because they are implemented
// properly in Visual Studio since 2015.
#if defined(_MSC_VER)
# define HAVE_SNPRINTF 1
#endif
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4505)
// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed
#endif
#include <Python.h>
#include <frameobject.h>
#include <pythread.h>
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
#if defined(PYBIND11_DEBUG_MARKER)
# define _DEBUG 1
# undef PYBIND11_DEBUG_MARKER
#endif
// Python #defines overrides on all sorts of core functions, which
// tends to wreak havok in C++ codebases that expect these to work
// like regular functions (potentially with several overloads).
#if defined(isalnum)
# undef isalnum
# undef isalpha
# undef islower
# undef isspace
# undef isupper
# undef tolower
# undef toupper
#endif
#if defined(copysign)
# undef copysign
#endif

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2016-2025 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "pytypes.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/// This does not do anything if there's a GIL. On free-threaded Python,
/// it locks an object. This uses the CPython API, which has limits
class scoped_critical_section {
public:
#ifdef Py_GIL_DISABLED
explicit scoped_critical_section(handle obj1, handle obj2 = handle{}) {
if (obj1) {
if (obj2) {
PyCriticalSection2_Begin(&section2, obj1.ptr(), obj2.ptr());
rank = 2;
} else {
PyCriticalSection_Begin(&section, obj1.ptr());
rank = 1;
}
} else if (obj2) {
PyCriticalSection_Begin(&section, obj2.ptr());
rank = 1;
}
}
~scoped_critical_section() {
if (rank == 1) {
PyCriticalSection_End(&section);
} else if (rank == 2) {
PyCriticalSection2_End(&section2);
}
}
#else
explicit scoped_critical_section(handle, handle = handle{}) {};
~scoped_critical_section() = default;
#endif
scoped_critical_section(const scoped_critical_section &) = delete;
scoped_critical_section &operator=(const scoped_critical_section &) = delete;
private:
#ifdef Py_GIL_DISABLED
int rank{0};
union {
PyCriticalSection section;
PyCriticalSection2 section2;
};
#endif
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,823 @@
/*
pybind11/detail/class.h: Python C API implementation details for py::class_
Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include <pybind11/attr.h>
#include <pybind11/options.h>
#include "exception_translation.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
#if !defined(PYPY_VERSION)
# define PYBIND11_BUILTIN_QUALNAME
# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj)
#else
// In PyPy, we still set __qualname__ so that we can produce reliable function type
// signatures; in CPython this macro expands to nothing:
# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) \
setattr((PyObject *) obj, "__qualname__", nameobj)
#endif
inline std::string get_fully_qualified_tp_name(PyTypeObject *type) {
#if !defined(PYPY_VERSION)
return type->tp_name;
#else
auto module_name = handle((PyObject *) type).attr("__module__").cast<std::string>();
if (module_name == PYBIND11_BUILTINS_MODULE)
return type->tp_name;
else
return std::move(module_name) + "." + type->tp_name;
#endif
}
inline PyTypeObject *type_incref(PyTypeObject *type) {
Py_INCREF(type);
return type;
}
#if !defined(PYPY_VERSION)
/// `pybind11_static_property.__get__()`: Always pass the class instead of the instance.
extern "C" inline PyObject *pybind11_static_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) {
return PyProperty_Type.tp_descr_get(self, cls, cls);
}
/// `pybind11_static_property.__set__()`: Just like the above `__get__()`.
extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObject *value) {
PyObject *cls = PyType_Check(obj) ? obj : (PyObject *) Py_TYPE(obj);
return PyProperty_Type.tp_descr_set(self, cls, value);
}
// Forward declaration to use in `make_static_property_type()`
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type);
/** A `static_property` is the same as a `property` but the `__get__()` and `__set__()`
methods are modified to always use the object type instead of a concrete instance.
Return value: New reference. */
inline PyTypeObject *make_static_property_type() {
constexpr auto *name = "pybind11_static_property";
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name));
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
if (!heap_type) {
pybind11_fail("make_static_property_type(): error allocating type!");
}
heap_type->ht_name = name_obj.inc_ref().ptr();
# ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = name_obj.inc_ref().ptr();
# endif
auto *type = &heap_type->ht_type;
type->tp_name = name;
type->tp_base = type_incref(&PyProperty_Type);
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
type->tp_descr_get = pybind11_static_get;
type->tp_descr_set = pybind11_static_set;
# if PY_VERSION_HEX >= 0x030C0000
// Since Python-3.12 property-derived types are required to
// have dynamic attributes (to set `__doc__`)
enable_dynamic_attributes(heap_type);
# endif
if (PyType_Ready(type) < 0) {
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
}
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
return type;
}
#else // PYPY
/** PyPy has some issues with the above C API, so we evaluate Python code instead.
This function will only be called once so performance isn't really a concern.
Return value: New reference. */
inline PyTypeObject *make_static_property_type() {
auto d = dict();
PyObject *result = PyRun_String(R"(\
class pybind11_static_property(property):
def __get__(self, obj, cls):
return property.__get__(self, cls, cls)
def __set__(self, obj, value):
cls = obj if isinstance(obj, type) else type(obj)
property.__set__(self, cls, value)
)",
Py_file_input,
d.ptr(),
d.ptr());
if (result == nullptr)
throw error_already_set();
Py_DECREF(result);
return (PyTypeObject *) d["pybind11_static_property"].cast<object>().release().ptr();
}
#endif // PYPY
/** Types with static properties need to handle `Type.static_prop = x` in a specific way.
By default, Python replaces the `static_property` itself, but for wrapped C++ types
we need to call `static_property.__set__()` in order to propagate the new value to
the underlying C++ data structure. */
extern "C" inline int pybind11_meta_setattro(PyObject *obj, PyObject *name, PyObject *value) {
// Use `_PyType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw
// descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`).
PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name);
// The following assignment combinations are possible:
// 1. `Type.static_prop = value` --> descr_set: `Type.static_prop.__set__(value)`
// 2. `Type.static_prop = other_static_prop` --> setattro: replace existing `static_prop`
// 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment
auto *const static_prop = (PyObject *) get_internals().static_property_type;
const auto call_descr_set = (descr != nullptr) && (value != nullptr)
&& (PyObject_IsInstance(descr, static_prop) != 0)
&& (PyObject_IsInstance(value, static_prop) == 0);
if (call_descr_set) {
// Call `static_property.__set__()` instead of replacing the `static_property`.
#if !defined(PYPY_VERSION)
return Py_TYPE(descr)->tp_descr_set(descr, obj, value);
#else
if (PyObject *result = PyObject_CallMethod(descr, "__set__", "OO", obj, value)) {
Py_DECREF(result);
return 0;
} else {
return -1;
}
#endif
} else {
// Replace existing attribute.
return PyType_Type.tp_setattro(obj, name, value);
}
}
/**
* Python 3's PyInstanceMethod_Type hides itself via its tp_descr_get, which prevents aliasing
* methods via cls.attr("m2") = cls.attr("m1"): instead the tp_descr_get returns a plain function,
* when called on a class, or a PyMethod, when called on an instance. Override that behaviour here
* to do a special case bypass for PyInstanceMethod_Types.
*/
extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name) {
PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name);
if (descr && PyInstanceMethod_Check(descr)) {
Py_INCREF(descr);
return descr;
}
return PyType_Type.tp_getattro(obj, name);
}
/// metaclass `__call__` function that is used to create all pybind11 objects.
extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) {
// use the default metaclass call to create/initialize the object
PyObject *self = PyType_Type.tp_call(type, args, kwargs);
if (self == nullptr) {
return nullptr;
}
// Ensure that the base __init__ function(s) were called
values_and_holders vhs(self);
for (const auto &vh : vhs) {
if (!vh.holder_constructed() && !vhs.is_redundant_value_and_holder(vh)) {
PyErr_Format(PyExc_TypeError,
"%.200s.__init__() must be called when overriding __init__",
get_fully_qualified_tp_name(vh.type->type).c_str());
Py_DECREF(self);
return nullptr;
}
}
return self;
}
/// Cleanup the type-info for a pybind11-registered type.
extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
with_internals([obj](internals &internals) {
auto *type = (PyTypeObject *) obj;
// A pybind11-registered type will:
// 1) be found in internals.registered_types_py
// 2) have exactly one associated `detail::type_info`
auto found_type = internals.registered_types_py.find(type);
if (found_type != internals.registered_types_py.end() && found_type->second.size() == 1
&& found_type->second[0]->type == type) {
auto *tinfo = found_type->second[0];
auto tindex = std::type_index(*tinfo->cpptype);
internals.direct_conversions.erase(tindex);
if (tinfo->module_local) {
get_local_internals().registered_types_cpp.erase(tindex);
} else {
internals.registered_types_cpp.erase(tindex);
}
internals.registered_types_py.erase(tinfo->type);
// Actually just `std::erase_if`, but that's only available in C++20
auto &cache = internals.inactive_override_cache;
for (auto it = cache.begin(), last = cache.end(); it != last;) {
if (it->first == (PyObject *) tinfo->type) {
it = cache.erase(it);
} else {
++it;
}
}
delete tinfo;
}
});
PyType_Type.tp_dealloc(obj);
}
/** This metaclass is assigned by default to all pybind11 types and is required in order
for static properties to function correctly. Users may override this using `py::metaclass`.
Return value: New reference. */
inline PyTypeObject *make_default_metaclass() {
constexpr auto *name = "pybind11_type";
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name));
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
if (!heap_type) {
pybind11_fail("make_default_metaclass(): error allocating metaclass!");
}
heap_type->ht_name = name_obj.inc_ref().ptr();
#ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = name_obj.inc_ref().ptr();
#endif
auto *type = &heap_type->ht_type;
type->tp_name = name;
type->tp_base = type_incref(&PyType_Type);
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
type->tp_call = pybind11_meta_call;
type->tp_setattro = pybind11_meta_setattro;
type->tp_getattro = pybind11_meta_getattro;
type->tp_dealloc = pybind11_meta_dealloc;
if (PyType_Ready(type) < 0) {
pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!");
}
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
return type;
}
/// For multiple inheritance types we need to recursively register/deregister base pointers for any
/// base classes with pointers that are difference from the instance value pointer so that we can
/// correctly recognize an offset base class pointer. This calls a function with any offset base
/// ptrs.
inline void traverse_offset_bases(void *valueptr,
const detail::type_info *tinfo,
instance *self,
bool (*f)(void * /*parentptr*/, instance * /*self*/)) {
for (handle h : reinterpret_borrow<tuple>(tinfo->type->tp_bases)) {
if (auto *parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) {
for (auto &c : parent_tinfo->implicit_casts) {
if (c.first == tinfo->cpptype) {
auto *parentptr = c.second(valueptr);
if (parentptr != valueptr) {
f(parentptr, self);
}
traverse_offset_bases(parentptr, parent_tinfo, self, f);
break;
}
}
}
}
}
#ifdef Py_GIL_DISABLED
inline void enable_try_inc_ref(PyObject *obj) {
// TODO: Replace with PyUnstable_Object_EnableTryIncRef when available.
// See https://github.com/python/cpython/issues/128844
if (_Py_IsImmortal(obj)) {
return;
}
for (;;) {
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared);
if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) {
// Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states.
return;
}
if (_Py_atomic_compare_exchange_ssize(
&obj->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) {
return;
}
}
}
#endif
inline bool register_instance_impl(void *ptr, instance *self) {
#ifdef Py_GIL_DISABLED
enable_try_inc_ref(reinterpret_cast<PyObject *>(self));
#endif
with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); });
return true; // unused, but gives the same signature as the deregister func
}
inline bool deregister_instance_impl(void *ptr, instance *self) {
return with_instance_map(ptr, [&](instance_map &instances) {
auto range = instances.equal_range(ptr);
for (auto it = range.first; it != range.second; ++it) {
if (self == it->second) {
instances.erase(it);
return true;
}
}
return false;
});
}
inline void register_instance(instance *self, void *valptr, const type_info *tinfo) {
register_instance_impl(valptr, self);
if (!tinfo->simple_ancestors) {
traverse_offset_bases(valptr, tinfo, self, register_instance_impl);
}
}
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo) {
bool ret = deregister_instance_impl(valptr, self);
if (!tinfo->simple_ancestors) {
traverse_offset_bases(valptr, tinfo, self, deregister_instance_impl);
}
return ret;
}
/// Instance creation function for all pybind11 types. It allocates the internal instance layout
/// for holding C++ objects and holders. Allocation is done lazily (the first time the instance is
/// cast to a reference or pointer), and initialization is done by an `__init__` function.
inline PyObject *make_new_instance(PyTypeObject *type) {
#if defined(PYPY_VERSION)
// PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first
// inherited object is a plain Python type (i.e. not derived from an extension type). Fix it.
ssize_t instance_size = static_cast<ssize_t>(sizeof(instance));
if (type->tp_basicsize < instance_size) {
type->tp_basicsize = instance_size;
}
#endif
PyObject *self = type->tp_alloc(type, 0);
auto *inst = reinterpret_cast<instance *>(self);
// Allocate the value/holder internals:
inst->allocate_layout();
return self;
}
/// Instance creation function for all pybind11 types. It only allocates space for the
/// C++ object, but doesn't call the constructor -- an `__init__` function must do that.
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) {
return make_new_instance(type);
}
/// An `__init__` function constructs the C++ object. Users should provide at least one
/// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the
/// following default function will be used which simply throws an exception.
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
PyTypeObject *type = Py_TYPE(self);
std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!";
set_error(PyExc_TypeError, msg.c_str());
return -1;
}
inline void add_patient(PyObject *nurse, PyObject *patient) {
auto *instance = reinterpret_cast<detail::instance *>(nurse);
instance->has_patients = true;
Py_INCREF(patient);
with_internals([&](internals &internals) { internals.patients[nurse].push_back(patient); });
}
inline void clear_patients(PyObject *self) {
auto *instance = reinterpret_cast<detail::instance *>(self);
std::vector<PyObject *> patients;
with_internals([&](internals &internals) {
auto pos = internals.patients.find(self);
if (pos == internals.patients.end()) {
pybind11_fail(
"FATAL: Internal consistency check failed: Invalid clear_patients() call.");
}
// Clearing the patients can cause more Python code to run, which
// can invalidate the iterator. Extract the vector of patients
// from the unordered_map first.
patients = std::move(pos->second);
internals.patients.erase(pos);
});
instance->has_patients = false;
for (PyObject *&patient : patients) {
Py_CLEAR(patient);
}
}
/// Clears all internal data from the instance and removes it from registered instances in
/// preparation for deallocation.
inline void clear_instance(PyObject *self) {
auto *instance = reinterpret_cast<detail::instance *>(self);
// Deallocate any values/holders, if present:
for (auto &v_h : values_and_holders(instance)) {
if (v_h) {
// We have to deregister before we call dealloc because, for virtual MI types, we still
// need to be able to get the parent pointers.
if (v_h.instance_registered()
&& !deregister_instance(instance, v_h.value_ptr(), v_h.type)) {
pybind11_fail(
"pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
}
if (instance->owned || v_h.holder_constructed()) {
v_h.type->dealloc(v_h);
}
} else if (v_h.holder_constructed()) {
v_h.type->dealloc(v_h); // Disowned instance.
}
}
// Deallocate the value/holder layout internals:
instance->deallocate_layout();
if (instance->weakrefs) {
PyObject_ClearWeakRefs(self);
}
PyObject **dict_ptr = _PyObject_GetDictPtr(self);
if (dict_ptr) {
Py_CLEAR(*dict_ptr);
}
if (instance->has_patients) {
clear_patients(self);
}
}
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
/// to destroy the C++ object itself, while the rest is Python bookkeeping.
extern "C" inline void pybind11_object_dealloc(PyObject *self) {
auto *type = Py_TYPE(self);
// If this is a GC tracked object, untrack it first
// Note that the track call is implicitly done by the
// default tp_alloc, which we never override.
if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) != 0) {
PyObject_GC_UnTrack(self);
}
clear_instance(self);
type->tp_free(self);
// This was not needed before Python 3.8 (Python issue 35810)
// https://github.com/pybind/pybind11/issues/1946
Py_DECREF(type);
}
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wredundant-decls")
std::string error_string();
PYBIND11_WARNING_POP
/** Create the type which can be used as a common base for all classes. This is
needed in order to satisfy Python's requirements for multiple inheritance.
Return value: New reference. */
inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
constexpr auto *name = "pybind11_object";
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name));
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
if (!heap_type) {
pybind11_fail("make_object_base_type(): error allocating type!");
}
heap_type->ht_name = name_obj.inc_ref().ptr();
#ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = name_obj.inc_ref().ptr();
#endif
auto *type = &heap_type->ht_type;
type->tp_name = name;
type->tp_base = type_incref(&PyBaseObject_Type);
type->tp_basicsize = static_cast<ssize_t>(sizeof(instance));
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
type->tp_new = pybind11_object_new;
type->tp_init = pybind11_object_init;
type->tp_dealloc = pybind11_object_dealloc;
/* Support weak references (needed for the keep_alive feature) */
type->tp_weaklistoffset = offsetof(instance, weakrefs);
if (PyType_Ready(type) < 0) {
pybind11_fail("PyType_Ready failed in make_object_base_type(): " + error_string());
}
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
return (PyObject *) heap_type;
}
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
#if PY_VERSION_HEX >= 0x030D0000
PyObject_VisitManagedDict(self, visit, arg);
#else
PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_VISIT(dict);
#endif
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse
#if PY_VERSION_HEX >= 0x03090000
Py_VISIT(Py_TYPE(self));
#endif
return 0;
}
/// dynamic_attr: Allow the GC to clear the dictionary.
extern "C" inline int pybind11_clear(PyObject *self) {
#if PY_VERSION_HEX >= 0x030D0000
PyObject_ClearManagedDict(self);
#else
PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_CLEAR(dict);
#endif
return 0;
}
/// Give instances of this type a `__dict__` and opt into garbage collection.
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
auto *type = &heap_type->ht_type;
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
type->tp_dictoffset = type->tp_basicsize; // place dict at the end
type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it
#else
type->tp_flags |= Py_TPFLAGS_MANAGED_DICT;
#endif
type->tp_traverse = pybind11_traverse;
type->tp_clear = pybind11_clear;
static PyGetSetDef getset[]
= {{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, nullptr, nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr}};
type->tp_getset = getset;
}
/// buffer_protocol: Fill in the view as specified by flags.
extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags) {
// Look for a `get_buffer` implementation in this type's info or any bases (following MRO).
type_info *tinfo = nullptr;
for (auto type : reinterpret_borrow<tuple>(Py_TYPE(obj)->tp_mro)) {
tinfo = get_type_info((PyTypeObject *) type.ptr());
if (tinfo && tinfo->get_buffer) {
break;
}
}
if (view == nullptr || !tinfo || !tinfo->get_buffer) {
if (view) {
view->obj = nullptr;
}
set_error(PyExc_BufferError, "pybind11_getbuffer(): Internal error");
return -1;
}
std::memset(view, 0, sizeof(Py_buffer));
std::unique_ptr<buffer_info> info = nullptr;
try {
info.reset(tinfo->get_buffer(obj, tinfo->get_buffer_data));
} catch (...) {
try_translate_exceptions();
raise_from(PyExc_BufferError, "Error getting buffer");
return -1;
}
if (info == nullptr) {
pybind11_fail("FATAL UNEXPECTED SITUATION: tinfo->get_buffer() returned nullptr.");
}
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
// view->obj = nullptr; // Was just memset to 0, so not necessary
set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
return -1;
}
// Fill in all the information, and then downgrade as requested by the caller, or raise an
// error if that's not possible.
view->itemsize = info->itemsize;
view->len = view->itemsize;
for (auto s : info->shape) {
view->len *= s;
}
view->ndim = static_cast<int>(info->ndim);
view->shape = info->shape.data();
view->strides = info->strides.data();
view->readonly = static_cast<int>(info->readonly);
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
view->format = const_cast<char *>(info->format.c_str());
}
// Note, all contiguity flags imply PyBUF_STRIDES and lower.
if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) {
if (PyBuffer_IsContiguous(view, 'C') == 0) {
std::memset(view, 0, sizeof(Py_buffer));
set_error(PyExc_BufferError,
"C-contiguous buffer requested for discontiguous storage");
return -1;
}
} else if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) {
if (PyBuffer_IsContiguous(view, 'F') == 0) {
std::memset(view, 0, sizeof(Py_buffer));
set_error(PyExc_BufferError,
"Fortran-contiguous buffer requested for discontiguous storage");
return -1;
}
} else if ((flags & PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS) {
if (PyBuffer_IsContiguous(view, 'A') == 0) {
std::memset(view, 0, sizeof(Py_buffer));
set_error(PyExc_BufferError, "Contiguous buffer requested for discontiguous storage");
return -1;
}
} else if ((flags & PyBUF_STRIDES) != PyBUF_STRIDES) {
// If no strides are requested, the buffer must be C-contiguous.
// https://docs.python.org/3/c-api/buffer.html#contiguity-requests
if (PyBuffer_IsContiguous(view, 'C') == 0) {
std::memset(view, 0, sizeof(Py_buffer));
set_error(PyExc_BufferError,
"C-contiguous buffer requested for discontiguous storage");
return -1;
}
view->strides = nullptr;
// Since this is a contiguous buffer, it can also pretend to be 1D.
if ((flags & PyBUF_ND) != PyBUF_ND) {
view->shape = nullptr;
view->ndim = 0;
}
}
// Set these after all checks so they don't leak out into the caller, and can be automatically
// cleaned up on error.
view->buf = info->ptr;
view->internal = info.release();
view->obj = obj;
Py_INCREF(view->obj);
return 0;
}
/// buffer_protocol: Release the resources of the buffer.
extern "C" inline void pybind11_releasebuffer(PyObject *, Py_buffer *view) {
delete (buffer_info *) view->internal;
}
/// Give this type a buffer interface.
inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) {
heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer;
heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer;
heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer;
}
/** Create a brand new Python type according to the `type_record` specification.
Return value: New reference. */
inline PyObject *make_new_python_type(const type_record &rec) {
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name));
auto qualname = name;
if (rec.scope && !PyModule_Check(rec.scope.ptr()) && hasattr(rec.scope, "__qualname__")) {
qualname = reinterpret_steal<object>(
PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr()));
}
object module_ = get_module_name_if_available(rec.scope);
const auto *full_name = c_str(
#if !defined(PYPY_VERSION)
module_ ? str(module_).cast<std::string>() + "." + rec.name :
#endif
rec.name);
char *tp_doc = nullptr;
if (rec.doc && options::show_user_defined_docstrings()) {
/* Allocate memory for docstring (Python will free this later on) */
size_t size = std::strlen(rec.doc) + 1;
#if PY_VERSION_HEX >= 0x030D0000
tp_doc = (char *) PyMem_MALLOC(size);
#else
tp_doc = (char *) PyObject_MALLOC(size);
#endif
std::memcpy((void *) tp_doc, rec.doc, size);
}
auto &internals = get_internals();
auto bases = tuple(rec.bases);
auto *base = (bases.empty()) ? internals.instance_base : bases[0].ptr();
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *metaclass
= rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr() : internals.default_metaclass;
auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
if (!heap_type) {
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
}
heap_type->ht_name = name.release().ptr();
#ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = qualname.inc_ref().ptr();
#endif
auto *type = &heap_type->ht_type;
type->tp_name = full_name;
type->tp_doc = tp_doc;
type->tp_base = type_incref((PyTypeObject *) base);
type->tp_basicsize = static_cast<ssize_t>(sizeof(instance));
if (!bases.empty()) {
type->tp_bases = bases.release().ptr();
}
/* Don't inherit base __init__ */
type->tp_init = pybind11_object_init;
/* Supported protocols */
type->tp_as_number = &heap_type->as_number;
type->tp_as_sequence = &heap_type->as_sequence;
type->tp_as_mapping = &heap_type->as_mapping;
type->tp_as_async = &heap_type->as_async;
/* Flags */
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE;
if (!rec.is_final) {
type->tp_flags |= Py_TPFLAGS_BASETYPE;
}
if (rec.dynamic_attr) {
enable_dynamic_attributes(heap_type);
}
if (rec.buffer_protocol) {
enable_buffer_protocol(heap_type);
}
if (rec.custom_type_setup_callback) {
rec.custom_type_setup_callback(heap_type);
}
if (PyType_Ready(type) < 0) {
pybind11_fail(std::string(rec.name) + ": PyType_Ready failed: " + error_string());
}
assert(!rec.dynamic_attr || PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
/* Register type with the parent scope */
if (rec.scope) {
setattr(rec.scope, rec.name, (PyObject *) type);
} else {
Py_INCREF(type); // Keep it alive forever (reference leak)
}
if (module_) { // Needed by pydoc
setattr((PyObject *) type, "__module__", module_);
}
PYBIND11_SET_OLDPY_QUALNAME(type, qualname);
return (PyObject *) type;
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
// Copyright (c) 2024 The pybind Community.
#pragma once
#include <pybind11/pytypes.h>
#include "common.h"
#include "internals.h"
#include <typeinfo>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
// Forward declaration needed here: Refactoring opportunity.
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *);
inline bool type_is_managed_by_our_internals(PyTypeObject *type_obj) {
#if defined(PYPY_VERSION)
auto &internals = get_internals();
return bool(internals.registered_types_py.find(type_obj)
!= internals.registered_types_py.end());
#else
return bool(type_obj->tp_new == pybind11_object_new);
#endif
}
inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) {
PyObject *descr = _PyType_Lookup(type_obj, attr_name);
return bool((descr != nullptr) && PyInstanceMethod_Check(descr));
}
inline object try_get_cpp_conduit_method(PyObject *obj) {
if (PyType_Check(obj)) {
return object();
}
PyTypeObject *type_obj = Py_TYPE(obj);
str attr_name("_pybind11_conduit_v1_");
bool assumed_to_be_callable = false;
if (type_is_managed_by_our_internals(type_obj)) {
if (!is_instance_method_of_type(type_obj, attr_name.ptr())) {
return object();
}
assumed_to_be_callable = true;
}
PyObject *method = PyObject_GetAttr(obj, attr_name.ptr());
if (method == nullptr) {
PyErr_Clear();
return object();
}
if (!assumed_to_be_callable && PyCallable_Check(method) == 0) {
Py_DECREF(method);
return object();
}
return reinterpret_steal<object>(method);
}
inline void *try_raw_pointer_ephemeral_from_cpp_conduit(handle src,
const std::type_info *cpp_type_info) {
object method = try_get_cpp_conduit_method(src.ptr());
if (method) {
capsule cpp_type_info_capsule(const_cast<void *>(static_cast<const void *>(cpp_type_info)),
typeid(std::type_info).name());
object cpp_conduit = method(bytes(PYBIND11_PLATFORM_ABI_ID),
cpp_type_info_capsule,
bytes("raw_pointer_ephemeral"));
if (isinstance<capsule>(cpp_conduit)) {
return reinterpret_borrow<capsule>(cpp_conduit).get_pointer();
}
}
return nullptr;
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,226 @@
/*
pybind11/detail/descr.h: Helper type for concatenating type signatures at compile time
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "common.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
#if !defined(_MSC_VER)
# define PYBIND11_DESCR_CONSTEXPR static constexpr
#else
# define PYBIND11_DESCR_CONSTEXPR const
#endif
/* Concatenate type signatures at compile time */
template <size_t N, typename... Ts>
struct descr {
char text[N + 1]{'\0'};
constexpr descr() = default;
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr descr(char const (&s)[N + 1]) : descr(s, make_index_sequence<N>()) {}
template <size_t... Is>
constexpr descr(char const (&s)[N + 1], index_sequence<Is...>) : text{s[Is]..., '\0'} {}
template <typename... Chars>
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr descr(char c, Chars... cs) : text{c, static_cast<char>(cs)..., '\0'} {}
static constexpr std::array<const std::type_info *, sizeof...(Ts) + 1> types() {
return {{&typeid(Ts)..., nullptr}};
}
};
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2, size_t... Is1, size_t... Is2>
constexpr descr<N1 + N2, Ts1..., Ts2...> plus_impl(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b,
index_sequence<Is1...>,
index_sequence<Is2...>) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(b);
return {a.text[Is1]..., b.text[Is2]...};
}
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2, Ts1..., Ts2...> operator+(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return plus_impl(a, b, make_index_sequence<N1>(), make_index_sequence<N2>());
}
template <size_t N>
constexpr descr<N - 1> const_name(char const (&text)[N]) {
return descr<N - 1>(text);
}
constexpr descr<0> const_name(char const (&)[1]) { return {}; }
template <size_t Rem, size_t... Digits>
struct int_to_str : int_to_str<Rem / 10, Rem % 10, Digits...> {};
template <size_t... Digits>
struct int_to_str<0, Digits...> {
// WARNING: This only works with C++17 or higher.
static constexpr auto digits = descr<sizeof...(Digits)>(('0' + Digits)...);
};
// Ternary description (like std::conditional)
template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<B, descr<N1 - 1>> const_name(char const (&text1)[N1], char const (&)[N2]) {
return const_name(text1);
}
template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<!B, descr<N2 - 1>> const_name(char const (&)[N1], char const (&text2)[N2]) {
return const_name(text2);
}
template <bool B, typename T1, typename T2>
constexpr enable_if_t<B, T1> const_name(const T1 &d, const T2 &) {
return d;
}
template <bool B, typename T1, typename T2>
constexpr enable_if_t<!B, T2> const_name(const T1 &, const T2 &d) {
return d;
}
template <size_t Size>
auto constexpr const_name() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> {
return int_to_str<Size / 10, Size % 10>::digits;
}
template <typename Type>
constexpr descr<1, Type> const_name() {
return {'%'};
}
// Use a different name based on whether the parameter is used as input or output
template <size_t N1, size_t N2>
constexpr descr<N1 + N2 + 1> io_name(char const (&text1)[N1], char const (&text2)[N2]) {
return const_name("@") + const_name(text1) + const_name("@") + const_name(text2)
+ const_name("@");
}
// Ternary description for io_name (like the numeric type_caster)
template <bool B, size_t N1, size_t N2, size_t N3, size_t N4>
constexpr enable_if_t<B, descr<N1 + N2 + 1>>
io_name(char const (&text1)[N1], char const (&text2)[N2], char const (&)[N3], char const (&)[N4]) {
return io_name(text1, text2);
}
template <bool B, size_t N1, size_t N2, size_t N3, size_t N4>
constexpr enable_if_t<!B, descr<N3 + N4 + 1>>
io_name(char const (&)[N1], char const (&)[N2], char const (&text3)[N3], char const (&text4)[N4]) {
return io_name(text3, text4);
}
// If "_" is defined as a macro, py::detail::_ cannot be provided.
// It is therefore best to use py::detail::const_name universally.
// This block is for backward compatibility only.
// (The const_name code is repeated to avoid introducing a "_" #define ourselves.)
#ifndef _
# define PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY
template <size_t N>
constexpr descr<N - 1> _(char const (&text)[N]) {
return const_name<N>(text);
}
template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<B, descr<N1 - 1>> _(char const (&text1)[N1], char const (&text2)[N2]) {
return const_name<B, N1, N2>(text1, text2);
}
template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<!B, descr<N2 - 1>> _(char const (&text1)[N1], char const (&text2)[N2]) {
return const_name<B, N1, N2>(text1, text2);
}
template <bool B, typename T1, typename T2>
constexpr enable_if_t<B, T1> _(const T1 &d1, const T2 &d2) {
return const_name<B, T1, T2>(d1, d2);
}
template <bool B, typename T1, typename T2>
constexpr enable_if_t<!B, T2> _(const T1 &d1, const T2 &d2) {
return const_name<B, T1, T2>(d1, d2);
}
template <size_t Size>
auto constexpr _() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> {
return const_name<Size>();
}
template <typename Type>
constexpr descr<1, Type> _() {
return const_name<Type>();
}
#endif // #ifndef _
constexpr descr<0> concat() { return {}; }
constexpr descr<0> union_concat() { return {}; }
template <size_t N, typename... Ts>
constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
return descr;
}
template <size_t N, typename... Ts>
constexpr descr<N, Ts...> union_concat(const descr<N, Ts...> &descr) {
return descr;
}
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 3, Ts1..., Ts2...> operator|(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return a + const_name(" | ") + b;
}
#ifdef __cpp_fold_expressions
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 2, Ts1..., Ts2...> operator,(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return a + const_name(", ") + b;
}
template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
return (d, ..., args);
}
template <size_t N, typename... Ts, typename... Args>
constexpr auto union_concat(const descr<N, Ts...> &d, const Args &...args) {
return (d | ... | args);
}
#else
template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
return d + const_name(", ") + concat(args...);
}
template <size_t N, typename... Ts, typename... Args>
constexpr auto union_concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 3, Ts...>>() + union_concat(args...)) {
return d + const_name(" | ") + union_concat(args...);
}
#endif
template <size_t N, typename... Ts>
constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {
return const_name("{") + descr + const_name("}");
}
template <size_t N, typename... Ts>
constexpr descr<N + 4, Ts...> arg_descr(const descr<N, Ts...> &descr) {
return const_name("@^") + descr + const_name("@!");
}
template <size_t N, typename... Ts>
constexpr descr<N + 4, Ts...> return_descr(const descr<N, Ts...> &descr) {
return const_name("@$") + descr + const_name("@!");
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2021 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "common.h"
#include <type_traits>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename To, typename From, typename SFINAE = void>
struct dynamic_raw_ptr_cast_is_possible : std::false_type {};
template <typename To, typename From>
struct dynamic_raw_ptr_cast_is_possible<
To,
From,
detail::enable_if_t<!std::is_same<To, void>::value && std::is_polymorphic<From>::value>>
: std::true_type {};
template <typename To,
typename From,
detail::enable_if_t<!dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) {
return nullptr;
}
template <typename To,
typename From,
detail::enable_if_t<dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
To *dynamic_raw_ptr_cast_if_possible(From *ptr) {
return dynamic_cast<To *>(ptr);
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,71 @@
/*
pybind11/detail/exception_translation.h: means to translate C++ exceptions to Python exceptions
Copyright (c) 2024 The Pybind Development Team.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "common.h"
#include "internals.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
// Apply all the extensions translators from a list
// Return true if one of the translators completed without raising an exception
// itself. Return of false indicates that if there are other translators
// available, they should be tried.
inline bool apply_exception_translators(std::forward_list<ExceptionTranslator> &translators) {
auto last_exception = std::current_exception();
for (auto &translator : translators) {
try {
translator(last_exception);
return true;
} catch (...) {
last_exception = std::current_exception();
}
}
return false;
}
inline void try_translate_exceptions() {
/* When an exception is caught, give each registered exception
translator a chance to translate it to a Python exception. First
all module-local translators will be tried in reverse order of
registration. If none of the module-locale translators handle
the exception (or there are no module-locale translators) then
the global translators will be tried, also in reverse order of
registration.
A translator may choose to do one of the following:
- catch the exception and call py::set_error()
to set a standard (or custom) Python exception, or
- do nothing and let the exception fall through to the next translator, or
- delegate translation to the next translator by throwing a new type of exception.
*/
bool handled = with_exception_translators(
[&](std::forward_list<ExceptionTranslator> &exception_translators,
std::forward_list<ExceptionTranslator> &local_exception_translators) {
if (detail::apply_exception_translators(local_exception_translators)) {
return true;
}
if (detail::apply_exception_translators(exception_translators)) {
return true;
}
return false;
});
if (!handled) {
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
}
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,191 @@
// Copyright (c) 2024-2025 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// For background see the description of PR google/pybind11clif#30099.
#pragma once
#include <pybind11/attr.h>
#include <pybind11/conduit/pybind11_platform_abi_id.h>
#include <pybind11/pytypes.h>
#include "common.h"
#include <cstring>
#include <utility>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
struct function_record_PyObject {
PyObject_HEAD
function_record *cpp_func_rec;
};
PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)
PyObject *tp_new_impl(PyTypeObject *type, PyObject *args, PyObject *kwds);
PyObject *tp_alloc_impl(PyTypeObject *type, Py_ssize_t nitems);
int tp_init_impl(PyObject *self, PyObject *args, PyObject *kwds);
void tp_dealloc_impl(PyObject *self);
void tp_free_impl(void *self);
static PyObject *reduce_ex_impl(PyObject *self, PyObject *, PyObject *);
static PyMethodDef tp_methods_impl[]
= {{"__reduce_ex__",
// reduce_ex_impl is a PyCFunctionWithKeywords, but PyMethodDef
// requires a PyCFunction. The cast through void* is safe and
// idiomatic with METH_KEYWORDS, and it successfully sidesteps
// unhelpful compiler warnings.
// NOLINTNEXTLINE(bugprone-casting-through-void)
reinterpret_cast<PyCFunction>(reinterpret_cast<void *>(reduce_ex_impl)),
METH_VARARGS | METH_KEYWORDS,
nullptr},
{nullptr, nullptr, 0, nullptr}};
// Python 3.12+ emits a DeprecationWarning for heap types whose tp_name does
// not contain a dot ('.') and that lack a __module__ attribute. For pybind11's
// internal function_record type, we do not have an actual module object to
// attach, so we cannot use PyType_FromModuleAndSpec (introduced in Python 3.9)
// to set __module__ automatically.
//
// As a workaround, we define a "qualified" type name that includes a dummy
// module name (PYBIND11_DUMMY_MODULE_NAME). This is nonidiomatic but avoids
// the deprecation warning, and results in reprs like
//
// <class 'pybind11_builtins.pybind11_detail_function_record_...'>
//
// even though no real pybind11_builtins module exists. If pybind11 gains an
// actual module object in the future, this code should switch to
// PyType_FromModuleAndSpec for Python 3.9+ and drop the dummy module
// workaround.
//
// Note that this name is versioned.
#define PYBIND11_DETAIL_FUNCTION_RECORD_TP_PLAINNAME \
"pybind11_detail_function_record_" PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID \
"_" PYBIND11_PLATFORM_ABI_ID
constexpr char tp_plainname_impl[] = PYBIND11_DETAIL_FUNCTION_RECORD_TP_PLAINNAME;
constexpr char tp_qualname_impl[]
= PYBIND11_DUMMY_MODULE_NAME "." PYBIND11_DETAIL_FUNCTION_RECORD_TP_PLAINNAME;
PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods)
static PyType_Slot function_record_PyType_Slots[] = {
{Py_tp_dealloc,
reinterpret_cast<void *>(function_record_PyTypeObject_methods::tp_dealloc_impl)},
{Py_tp_methods,
reinterpret_cast<void *>(function_record_PyTypeObject_methods::tp_methods_impl)},
{Py_tp_init, reinterpret_cast<void *>(function_record_PyTypeObject_methods::tp_init_impl)},
{Py_tp_alloc, reinterpret_cast<void *>(function_record_PyTypeObject_methods::tp_alloc_impl)},
{Py_tp_new, reinterpret_cast<void *>(function_record_PyTypeObject_methods::tp_new_impl)},
{Py_tp_free, reinterpret_cast<void *>(function_record_PyTypeObject_methods::tp_free_impl)},
{0, nullptr}};
static PyType_Spec function_record_PyType_Spec
= {function_record_PyTypeObject_methods::tp_qualname_impl,
sizeof(function_record_PyObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE,
function_record_PyType_Slots};
inline PyTypeObject *get_function_record_PyTypeObject() {
PyTypeObject *&py_type_obj = detail::get_local_internals().function_record_py_type;
if (!py_type_obj) {
PyObject *py_obj = PyType_FromSpec(&function_record_PyType_Spec);
if (py_obj == nullptr) {
throw error_already_set();
}
py_type_obj = reinterpret_cast<PyTypeObject *>(py_obj);
}
return py_type_obj;
}
inline bool is_function_record_PyObject(PyObject *obj) {
if (PyType_Check(obj) != 0) {
return false;
}
PyTypeObject *obj_type = Py_TYPE(obj);
PyTypeObject *frtype = get_function_record_PyTypeObject();
// Fast path (pointer comparison).
if (obj_type == frtype) {
return true;
}
// This works across extension modules. Note that tp_name is versioned.
if (strcmp(obj_type->tp_name, function_record_PyTypeObject_methods::tp_qualname_impl) == 0
|| strcmp(obj_type->tp_name, function_record_PyTypeObject_methods::tp_plainname_impl)
== 0) {
return true;
}
return false;
}
inline function_record *function_record_ptr_from_PyObject(PyObject *obj) {
if (is_function_record_PyObject(obj)) {
return ((detail::function_record_PyObject *) obj)->cpp_func_rec;
}
return nullptr;
}
inline object function_record_PyObject_New() {
auto *py_func_rec = PyObject_New(function_record_PyObject, get_function_record_PyTypeObject());
if (py_func_rec == nullptr) {
throw error_already_set();
}
py_func_rec->cpp_func_rec = nullptr; // For clarity/purity. Redundant in practice.
return reinterpret_steal<object>((PyObject *) py_func_rec);
}
PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)
// Guard against accidents & oversights, in particular when porting to future Python versions.
inline PyObject *tp_new_impl(PyTypeObject *, PyObject *, PyObject *) {
pybind11_fail("UNEXPECTED CALL OF function_record_PyTypeObject_methods::tp_new_impl");
// return nullptr; // Unreachable.
}
inline PyObject *tp_alloc_impl(PyTypeObject *, Py_ssize_t) {
pybind11_fail("UNEXPECTED CALL OF function_record_PyTypeObject_methods::tp_alloc_impl");
// return nullptr; // Unreachable.
}
inline int tp_init_impl(PyObject *, PyObject *, PyObject *) {
pybind11_fail("UNEXPECTED CALL OF function_record_PyTypeObject_methods::tp_init_impl");
// return -1; // Unreachable.
}
inline void tp_free_impl(void *) {
pybind11_fail("UNEXPECTED CALL OF function_record_PyTypeObject_methods::tp_free_impl");
}
inline PyObject *reduce_ex_impl(PyObject *self, PyObject *, PyObject *) {
// Deliberately ignoring the arguments for simplicity (expected is `protocol: int`).
const function_record *rec = function_record_ptr_from_PyObject(self);
if (rec == nullptr) {
pybind11_fail(
"FATAL: function_record_PyTypeObject reduce_ex_impl(): cannot obtain cpp_func_rec.");
}
if (rec->name != nullptr && rec->name[0] != '\0' && rec->scope
&& PyModule_Check(rec->scope.ptr()) != 0) {
object scope_module = get_scope_module(rec->scope);
if (scope_module) {
auto builtins = reinterpret_borrow<dict>(PyEval_GetBuiltins());
auto builtins_eval = builtins["eval"];
auto reconstruct_args = make_tuple(str("__import__('importlib').import_module('")
+ scope_module + str("')"));
return make_tuple(std::move(builtins_eval), std::move(reconstruct_args))
.release()
.ptr();
}
}
set_error(PyExc_RuntimeError, repr(self) + str(" is not pickleable."));
return nullptr;
}
PYBIND11_NAMESPACE_END(function_record_PyTypeObject_methods)
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,538 @@
/*
pybind11/detail/init.h: init factory function implementation and support code.
Copyright (c) 2017 Jason Rhinelander <jason@imaginary.ca>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "class.h"
#include "using_smart_holder.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<value_and_holder> {
public:
bool load(handle h, bool) {
value = reinterpret_cast<value_and_holder *>(h.ptr());
return true;
}
template <typename>
using cast_op_type = value_and_holder &;
explicit operator value_and_holder &() { return *value; }
static constexpr auto name = const_name<value_and_holder>();
private:
value_and_holder *value = nullptr;
};
PYBIND11_NAMESPACE_BEGIN(initimpl)
inline void no_nullptr(const void *ptr) {
if (!ptr) {
throw type_error("pybind11::init(): factory function returned nullptr");
}
}
// Implementing functions for all forms of py::init<...> and py::init(...)
template <typename Class>
using Cpp = typename Class::type;
template <typename Class>
using Alias = typename Class::type_alias;
template <typename Class>
using Holder = typename Class::holder_type;
template <typename Class>
using is_alias_constructible = std::is_constructible<Alias<Class>, Cpp<Class> &&>;
// Takes a Cpp pointer and returns true if it actually is a polymorphic Alias instance.
template <typename Class, enable_if_t<Class::has_alias, int> = 0>
bool is_alias(Cpp<Class> *ptr) {
return dynamic_cast<Alias<Class> *>(ptr) != nullptr;
}
// Failing fallback version of the above for a no-alias class (always returns false)
template <typename /*Class*/>
constexpr bool is_alias(const void *) {
return false;
}
// Constructs and returns a new object; if the given arguments don't map to a constructor, we fall
// back to brace aggregate initialization so that for aggregate initialization can be used with
// py::init, e.g. `py::init<int, int>` to initialize a `struct T { int a; int b; }`. For
// non-aggregate types, we need to use an ordinary T(...) constructor (invoking as `T{...}` usually
// works, but will not do the expected thing when `T` has an `initializer_list<T>` constructor).
template <typename Class,
typename... Args,
detail::enable_if_t<std::is_constructible<Class, Args...>::value, int> = 0>
inline Class *construct_or_initialize(Args &&...args) {
return new Class(std::forward<Args>(args)...);
}
template <typename Class,
typename... Args,
detail::enable_if_t<!std::is_constructible<Class, Args...>::value, int> = 0>
inline Class *construct_or_initialize(Args &&...args) {
return new Class{std::forward<Args>(args)...};
}
// Attempts to constructs an alias using a `Alias(Cpp &&)` constructor. This allows types with
// an alias to provide only a single Cpp factory function as long as the Alias can be
// constructed from an rvalue reference of the base Cpp type. This means that Alias classes
// can, when appropriate, simply define a `Alias(Cpp &&)` constructor rather than needing to
// inherit all the base class constructors.
template <typename Class>
void construct_alias_from_cpp(std::true_type /*is_alias_constructible*/,
value_and_holder &v_h,
Cpp<Class> &&base) {
v_h.value_ptr() = new Alias<Class>(std::move(base));
}
template <typename Class>
[[noreturn]] void construct_alias_from_cpp(std::false_type /*!is_alias_constructible*/,
value_and_holder &,
Cpp<Class> &&) {
throw type_error("pybind11::init(): unable to convert returned instance to required "
"alias class: no `Alias<Class>(Class &&)` constructor available");
}
// Error-generating fallback for factories that don't match one of the below construction
// mechanisms.
template <typename Class>
void construct(...) {
static_assert(!std::is_same<Class, Class>::value /* always false */,
"pybind11::init(): init function must return a compatible pointer, "
"holder, or value");
}
// Pointer return v1: the factory function returns a class pointer for a registered class.
// If we don't need an alias (because this class doesn't have one, or because the final type is
// inherited on the Python side) we can simply take over ownership. Otherwise we need to try to
// construct an Alias from the returned base instance.
template <typename Class>
void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
no_nullptr(ptr);
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
// We're going to try to construct an alias by moving the cpp type. Whether or not
// that succeeds, we still need to destroy the original cpp pointer (either the
// moved away leftover, if the alias construction works, or the value itself if we
// throw an error), but we can't just call `delete ptr`: it might have a special
// deleter, or might be shared_from_this. So we construct a holder around it as if
// it was a normal instance, then steal the holder away into a local variable; thus
// the holder and destruction happens when we leave the C++ scope, and the holder
// class gets to handle the destruction however it likes.
v_h.value_ptr() = ptr;
v_h.set_instance_registered(true); // Trick to prevent init_instance from registering it
// DANGER ZONE BEGIN: exceptions will leave v_h in an invalid state.
v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder
Holder<Class> temp_holder(std::move(v_h.holder<Holder<Class>>())); // Steal the holder
v_h.type->dealloc(v_h); // Destroys the moved-out holder remains, resets value ptr to null
v_h.set_instance_registered(false);
// DANGER ZONE END.
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(*ptr));
} else {
// Otherwise the type isn't inherited, so we don't need an Alias
v_h.value_ptr() = ptr;
}
}
// Pointer return v2: a factory that always returns an alias instance ptr. We simply take over
// ownership of the pointer.
template <typename Class, enable_if_t<Class::has_alias, int> = 0>
void construct(value_and_holder &v_h, Alias<Class> *alias_ptr, bool) {
no_nullptr(alias_ptr);
v_h.value_ptr() = static_cast<Cpp<Class> *>(alias_ptr);
}
// Holder return: copy its pointer, and move or copy the returned holder into the new instance's
// holder. This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
// derived type (through those holder's implicit conversion from derived class holder
// constructors).
template <typename Class, detail::enable_if_t<!is_smart_holder<Holder<Class>>::value, int> = 0>
void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
auto *ptr = holder_helper<Holder<Class>>::get(holder);
no_nullptr(ptr);
// If we need an alias, check that the held pointer is actually an alias instance
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance "
"is not an alias instance");
}
// Cast away constness to store in void* storage.
// The value_and_holder storage is fundamentally untyped (void**), so we lose
// const-correctness here by design. The const qualifier will be restored
// when the pointer is later retrieved and cast back to the original type.
// This explicit const_cast makes the const-removal clearly visible.
v_h.value_ptr() = const_cast<void *>(static_cast<const void *>(ptr));
v_h.type->init_instance(v_h.inst, &holder);
}
// return-by-value version 1: returning a cpp class by value. If the class has an alias and an
// alias is required the alias must have an `Alias(Cpp &&)` constructor so that we can construct
// the alias from the base when needed (i.e. because of Python-side inheritance). When we don't
// need it, we simply move-construct the cpp value into a new instance.
template <typename Class>
void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
static_assert(is_move_constructible<Cpp<Class>>::value,
"pybind11::init() return-by-value factory function requires a movable class");
if (Class::has_alias && need_alias) {
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result));
} else {
v_h.value_ptr() = new Cpp<Class>(std::move(result));
}
}
// return-by-value version 2: returning a value of the alias type itself. We move-construct an
// Alias instance (even if no the python-side inheritance is involved). The is intended for
// cases where Alias initialization is always desired.
template <typename Class>
void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
static_assert(
is_move_constructible<Alias<Class>>::value,
"pybind11::init() return-by-alias-value factory function requires a movable alias class");
v_h.value_ptr() = new Alias<Class>(std::move(result));
}
template <typename T, typename D>
smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
bool void_cast_raw_ptr) {
void *void_ptr = void_cast_raw_ptr ? static_cast<void *>(unq_ptr.get()) : nullptr;
return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr);
}
template <typename Class,
typename D = std::default_delete<Cpp<Class>>,
detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
void construct(value_and_holder &v_h, std::unique_ptr<Cpp<Class>, D> &&unq_ptr, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
auto *ptr = unq_ptr.get();
no_nullptr(ptr);
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee "
"is not an alias instance");
}
// Here and below: if the new object is a trampoline, the shared_from_this mechanism needs
// to be prevented from accessing the smart_holder vptr, because it does not keep the
// trampoline Python object alive. For types that don't inherit from enable_shared_from_this
// it does not matter if void_cast_raw_ptr is true or false, therefore it's not necessary
// to also inspect the type.
auto smhldr = init_smart_holder_from_unique_ptr(
std::move(unq_ptr), /*void_cast_raw_ptr*/ Class::has_alias && is_alias<Class>(ptr));
v_h.value_ptr() = ptr;
v_h.type->init_instance(v_h.inst, &smhldr);
}
template <typename Class,
typename D = std::default_delete<Alias<Class>>,
detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
void construct(value_and_holder &v_h,
std::unique_ptr<Alias<Class>, D> &&unq_ptr,
bool /*need_alias*/) {
auto *ptr = unq_ptr.get();
no_nullptr(ptr);
auto smhldr
= init_smart_holder_from_unique_ptr(std::move(unq_ptr), /*void_cast_raw_ptr*/ true);
v_h.value_ptr() = ptr;
v_h.type->init_instance(v_h.inst, &smhldr);
}
template <typename PtrType, typename Class>
void construct_from_shared_ptr(value_and_holder &v_h,
std::shared_ptr<PtrType> &&shd_ptr,
bool need_alias) {
static_assert(std::is_same<PtrType, Cpp<Class>>::value
|| std::is_same<PtrType, const Cpp<Class>>::value,
"Expected (const) Cpp<Class> as shared_ptr pointee");
auto *ptr = shd_ptr.get();
no_nullptr(ptr);
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee "
"is not an alias instance");
}
// Cast to non-const if needed, consistent with internal design
auto smhldr
= smart_holder::from_shared_ptr(std::const_pointer_cast<Cpp<Class>>(std::move(shd_ptr)));
v_h.value_ptr() = const_cast<Cpp<Class> *>(ptr);
v_h.type->init_instance(v_h.inst, &smhldr);
}
template <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
void construct(value_and_holder &v_h, std::shared_ptr<Cpp<Class>> &&shd_ptr, bool need_alias) {
construct_from_shared_ptr<Cpp<Class>, Class>(v_h, std::move(shd_ptr), need_alias);
}
template <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
void construct(value_and_holder &v_h,
std::shared_ptr<const Cpp<Class>> &&shd_ptr,
bool need_alias) {
construct_from_shared_ptr<const Cpp<Class>, Class>(v_h, std::move(shd_ptr), need_alias);
}
template <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
void construct(value_and_holder &v_h,
std::shared_ptr<Alias<Class>> &&shd_ptr,
bool /*need_alias*/) {
auto *ptr = shd_ptr.get();
no_nullptr(ptr);
auto smhldr = smart_holder::from_shared_ptr(shd_ptr);
v_h.value_ptr() = ptr;
v_h.type->init_instance(v_h.inst, &smhldr);
}
// Implementing class for py::init<...>()
template <typename... Args>
struct constructor {
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
[](value_and_holder &v_h,
Args... args) { // NOLINT(performance-unnecessary-value-param)
v_h.value_ptr() = construct_or_initialize<Cpp<Class>>(std::forward<Args>(args)...);
},
is_new_style_constructor(),
extra...);
}
template <
typename Class,
typename... Extra,
enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
[](value_and_holder &v_h, Args... args) {
if (Py_TYPE(v_h.inst) == v_h.type->type) {
v_h.value_ptr()
= construct_or_initialize<Cpp<Class>>(std::forward<Args>(args)...);
} else {
v_h.value_ptr()
= construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...);
}
},
is_new_style_constructor(),
extra...);
}
template <
typename Class,
typename... Extra,
enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
[](value_and_holder &v_h, Args... args) {
v_h.value_ptr()
= construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...);
},
is_new_style_constructor(),
extra...);
}
};
// Implementing class for py::init_alias<...>()
template <typename... Args>
struct alias_constructor {
template <
typename Class,
typename... Extra,
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
[](value_and_holder &v_h, Args... args) {
v_h.value_ptr()
= construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...);
},
is_new_style_constructor(),
extra...);
}
};
// Implementation class for py::init(Func) and py::init(Func, AliasFunc)
template <typename CFunc,
typename AFunc = void_type (*)(),
typename = function_signature_t<CFunc>,
typename = function_signature_t<AFunc>>
struct factory;
// Specialization for py::init(Func)
template <typename Func, typename Return, typename... Args>
struct factory<Func, void_type (*)(), Return(Args...)> {
remove_reference_t<Func> class_factory;
// NOLINTNEXTLINE(google-explicit-constructor)
factory(Func &&f) : class_factory(std::forward<Func>(f)) {}
// The given class either has no alias or has no separate alias factory;
// this always constructs the class itself. If the class is registered with an alias
// type and an alias instance is needed (i.e. because the final type is a Python class
// inheriting from the C++ type) the returned value needs to either already be an alias
// instance, or the alias needs to be constructible from a `Class &&` argument.
template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) && {
#if defined(PYBIND11_CPP14)
cl.def(
"__init__",
[func = std::move(class_factory)]
#else
auto &func = class_factory;
cl.def(
"__init__",
[func]
#endif
(value_and_holder &v_h, Args... args) {
construct<Class>(
v_h, func(std::forward<Args>(args)...), Py_TYPE(v_h.inst) != v_h.type->type);
},
is_new_style_constructor(),
extra...);
}
};
// Specialization for py::init(Func, AliasFunc)
template <typename CFunc,
typename AFunc,
typename CReturn,
typename... CArgs,
typename AReturn,
typename... AArgs>
struct factory<CFunc, AFunc, CReturn(CArgs...), AReturn(AArgs...)> {
static_assert(sizeof...(CArgs) == sizeof...(AArgs),
"pybind11::init(class_factory, alias_factory): class and alias factories "
"must have identical argument signatures");
static_assert(all_of<std::is_same<CArgs, AArgs>...>::value,
"pybind11::init(class_factory, alias_factory): class and alias factories "
"must have identical argument signatures");
remove_reference_t<CFunc> class_factory;
remove_reference_t<AFunc> alias_factory;
factory(CFunc &&c, AFunc &&a)
: class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) {}
// The class factory is called when the `self` type passed to `__init__` is the direct
// class (i.e. not inherited), the alias factory when `self` is a Python-side subtype.
template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) && {
static_assert(Class::has_alias,
"The two-argument version of `py::init()` can "
"only be used if the class has an alias");
#if defined(PYBIND11_CPP14)
cl.def(
"__init__",
[class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
#else
auto &class_func = class_factory;
auto &alias_func = alias_factory;
cl.def(
"__init__",
[class_func, alias_func]
#endif
(value_and_holder &v_h, CArgs... args) {
if (Py_TYPE(v_h.inst) == v_h.type->type) {
// If the instance type equals the registered type we don't have inheritance,
// so don't need the alias and can construct using the class function:
construct<Class>(v_h, class_func(std::forward<CArgs>(args)...), false);
} else {
construct<Class>(v_h, alias_func(std::forward<CArgs>(args)...), true);
}
},
is_new_style_constructor(),
extra...);
}
};
/// Set just the C++ state. Same as `__init__`.
template <typename Class, typename T>
void setstate(value_and_holder &v_h, T &&result, bool need_alias) {
construct<Class>(v_h, std::forward<T>(result), need_alias);
}
/// Set both the C++ and Python states
template <typename Class,
typename T,
typename O,
enable_if_t<std::is_convertible<O, handle>::value, int> = 0>
void setstate(value_and_holder &v_h, std::pair<T, O> &&result, bool need_alias) {
construct<Class>(v_h, std::move(result.first), need_alias);
auto d = handle(result.second);
if (PyDict_Check(d.ptr()) && PyDict_Size(d.ptr()) == 0) {
// Skipping setattr below, to not force use of py::dynamic_attr() for Class unnecessarily.
// See PR #2972 for details.
return;
}
// Our tests never run into an unset dict, but being careful here for now (see #5658)
auto dict = getattr((PyObject *) v_h.inst, "__dict__", none());
if (dict.is_none()) {
setattr((PyObject *) v_h.inst, "__dict__", d);
} else {
// Keep the original object dict and just update it
if (PyDict_Update(dict.ptr(), d.ptr()) < 0) {
throw error_already_set();
}
}
}
/// Implementation for py::pickle(GetState, SetState)
template <typename Get,
typename Set,
typename = function_signature_t<Get>,
typename = function_signature_t<Set>>
struct pickle_factory;
template <typename Get,
typename Set,
typename RetState,
typename Self,
typename NewInstance,
typename ArgState>
struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> {
static_assert(std::is_same<intrinsic_t<RetState>, intrinsic_t<ArgState>>::value,
"The type returned by `__getstate__` must be the same "
"as the argument accepted by `__setstate__`");
remove_reference_t<Get> get;
remove_reference_t<Set> set;
pickle_factory(Get get, Set set) : get(std::forward<Get>(get)), set(std::forward<Set>(set)) {}
template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) && {
cl.def("__getstate__", std::move(get), pos_only());
#if defined(PYBIND11_CPP14)
cl.def(
"__setstate__",
[func = std::move(set)]
#else
auto &func = set;
cl.def(
"__setstate__",
[func]
#endif
(value_and_holder &v_h, ArgState state) {
setstate<Class>(
v_h, func(std::forward<ArgState>(state)), Py_TYPE(v_h.inst) != v_h.type->type);
},
is_new_style_constructor(),
extra...);
}
};
PYBIND11_NAMESPACE_END(initimpl)
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,799 @@
/*
pybind11/detail/internals.h: Internal data structure and related functions
Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include <pybind11/conduit/pybind11_platform_abi_id.h>
#include <pybind11/gil_simple.h>
#include <pybind11/pytypes.h>
#include <pybind11/trampoline_self_life_support.h>
#include "common.h"
#include "struct_smart_holder.h"
#include <atomic>
#include <cstdint>
#include <exception>
#include <limits>
#include <mutex>
#include <thread>
/// Tracks the `internals` and `type_info` ABI version independent of the main library version.
///
/// Some portions of the code use an ABI that is conditional depending on this
/// version number. That allows ABI-breaking changes to be "pre-implemented".
/// Once the default version number is incremented, the conditional logic that
/// no longer applies can be removed. Additionally, users that need not
/// maintain ABI compatibility can increase the version number in order to take
/// advantage of any functionality/efficiency improvements that depend on the
/// newer ABI.
///
/// WARNING: If you choose to manually increase the ABI version, note that
/// pybind11 may not be tested as thoroughly with a non-default ABI version, and
/// further ABI-incompatible changes may be made before the ABI is officially
/// changed to the new version.
#ifndef PYBIND11_INTERNALS_VERSION
# define PYBIND11_INTERNALS_VERSION 11
#endif
#if PYBIND11_INTERNALS_VERSION < 11
# error "PYBIND11_INTERNALS_VERSION 11 is the minimum for all platforms for pybind11v3."
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
using ExceptionTranslator = void (*)(std::exception_ptr);
// The old Python Thread Local Storage (TLS) API is deprecated in Python 3.7 in favor of the new
// Thread Specific Storage (TSS) API.
// Avoid unnecessary allocation of `Py_tss_t`, since we cannot use
// `Py_LIMITED_API` anyway.
#define PYBIND11_TLS_KEY_REF Py_tss_t &
#if defined(__clang__)
# define PYBIND11_TLS_KEY_INIT(var) \
_Pragma("clang diagnostic push") /**/ \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
Py_tss_t var \
= Py_tss_NEEDS_INIT; \
_Pragma("clang diagnostic pop")
#elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
# define PYBIND11_TLS_KEY_INIT(var) \
_Pragma("GCC diagnostic push") /**/ \
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
Py_tss_t var \
= Py_tss_NEEDS_INIT; \
_Pragma("GCC diagnostic pop")
#else
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT;
#endif
#define PYBIND11_TLS_KEY_CREATE(var) (PyThread_tss_create(&(var)) == 0)
#define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get(&(key))
#define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set(&(key), (value))
#define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set(&(key), nullptr)
#define PYBIND11_TLS_FREE(key) PyThread_tss_delete(&(key))
/// A smart-pointer-like wrapper around a thread-specific value. get/set of the pointer applies to
/// the current thread only.
template <typename T>
class thread_specific_storage {
public:
thread_specific_storage() {
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(key_)) {
pybind11_fail(
"thread_specific_storage constructor: could not initialize the TSS key!");
}
}
~thread_specific_storage() {
// This destructor is often called *after* Py_Finalize(). That *SHOULD BE* fine on most
// platforms. The following details what happens when PyThread_tss_free is called in
// CPython. PYBIND11_TLS_FREE is PyThread_tss_free on python 3.7+. On older python, it does
// nothing. PyThread_tss_free calls PyThread_tss_delete and PyMem_RawFree.
// PyThread_tss_delete just calls TlsFree (on Windows) or pthread_key_delete (on *NIX).
// Neither of those have anything to do with CPython internals. PyMem_RawFree *requires*
// that the `key` be allocated with the CPython allocator (as it is by
// PyThread_tss_create).
// However, in GraalPy (as of v24.2 or older), TSS is implemented by Java and this call
// requires a living Python interpreter.
#ifdef GRAALVM_PYTHON
if (!Py_IsInitialized() || _Py_IsFinalizing()) {
return;
}
#endif
PYBIND11_TLS_FREE(key_);
}
thread_specific_storage(thread_specific_storage const &) = delete;
thread_specific_storage(thread_specific_storage &&) = delete;
thread_specific_storage &operator=(thread_specific_storage const &) = delete;
thread_specific_storage &operator=(thread_specific_storage &&) = delete;
T *get() const { return reinterpret_cast<T *>(PYBIND11_TLS_GET_VALUE(key_)); }
T &operator*() const { return *get(); }
explicit operator T *() const { return get(); }
explicit operator bool() const { return get() != nullptr; }
void set(T *val) { PYBIND11_TLS_REPLACE_VALUE(key_, reinterpret_cast<void *>(val)); }
void reset(T *p = nullptr) { set(p); }
thread_specific_storage &operator=(T *pval) {
set(pval);
return *this;
}
private:
PYBIND11_TLS_KEY_INIT(mutable key_)
};
PYBIND11_NAMESPACE_BEGIN(detail)
// This does NOT actually exist as a module.
#define PYBIND11_DUMMY_MODULE_NAME "pybind11_builtins"
// Forward declarations
inline PyTypeObject *make_static_property_type();
inline PyTypeObject *make_default_metaclass();
inline PyObject *make_object_base_type(PyTypeObject *metaclass);
inline void translate_exception(std::exception_ptr p);
// Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly
// other STLs, this means `typeid(A)` from one module won't equal `typeid(A)` from another module
// even when `A` is the same, non-hidden-visibility type (e.g. from a common include). Under
// libstdc++, this doesn't happen: equality and the type_index hash are based on the type name,
// which works. If not under a known-good stl, provide our own name-based hash and equality
// functions that use the type name.
#if !defined(_LIBCPP_VERSION)
inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; }
using type_hash = std::hash<std::type_index>;
using type_equal_to = std::equal_to<std::type_index>;
#else
inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) {
return lhs.name() == rhs.name() || std::strcmp(lhs.name(), rhs.name()) == 0;
}
struct type_hash {
size_t operator()(const std::type_index &t) const {
size_t hash = 5381;
const char *ptr = t.name();
while (auto c = static_cast<unsigned char>(*ptr++)) {
hash = (hash * 33) ^ c;
}
return hash;
}
};
struct type_equal_to {
bool operator()(const std::type_index &lhs, const std::type_index &rhs) const {
return lhs.name() == rhs.name() || std::strcmp(lhs.name(), rhs.name()) == 0;
}
};
#endif
template <typename value_type>
using type_map = std::unordered_map<std::type_index, value_type, type_hash, type_equal_to>;
struct override_hash {
inline size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
size_t value = std::hash<const void *>()(v.first);
value ^= std::hash<const void *>()(v.second) + 0x9e3779b9 + (value << 6) + (value >> 2);
return value;
}
};
using instance_map = std::unordered_multimap<const void *, instance *>;
#ifdef Py_GIL_DISABLED
// Wrapper around PyMutex to provide BasicLockable semantics
class pymutex {
PyMutex mutex;
public:
pymutex() : mutex({}) {}
void lock() { PyMutex_Lock(&mutex); }
void unlock() { PyMutex_Unlock(&mutex); }
};
// Instance map shards are used to reduce mutex contention in free-threaded Python.
struct instance_map_shard {
instance_map registered_instances;
pymutex mutex;
// alignas(64) would be better, but causes compile errors in macOS before 10.14 (see #5200)
char padding[64 - (sizeof(instance_map) + sizeof(pymutex)) % 64];
};
static_assert(sizeof(instance_map_shard) % 64 == 0,
"instance_map_shard size is not a multiple of 64 bytes");
inline uint64_t round_up_to_next_pow2(uint64_t x) {
// Round-up to the next power of two.
// See https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
x--;
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
x |= (x >> 32);
x++;
return x;
}
#endif
class loader_life_support;
/// Internal data structure used to track registered instances and types.
/// Whenever binary incompatible changes are made to this structure,
/// `PYBIND11_INTERNALS_VERSION` must be incremented.
struct internals {
#ifdef Py_GIL_DISABLED
pymutex mutex;
pymutex exception_translator_mutex;
#endif
// std::type_index -> pybind11's type information
type_map<type_info *> registered_types_cpp;
// PyTypeObject* -> base type_info(s)
std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py;
#ifdef Py_GIL_DISABLED
std::unique_ptr<instance_map_shard[]> instance_shards; // void * -> instance*
size_t instance_shards_mask = 0;
#else
instance_map registered_instances; // void * -> instance*
#endif
std::unordered_set<std::pair<const PyObject *, const char *>, override_hash>
inactive_override_cache;
type_map<std::vector<bool (*)(PyObject *, void *&)>> direct_conversions;
std::unordered_map<const PyObject *, std::vector<PyObject *>> patients;
std::forward_list<ExceptionTranslator> registered_exception_translators;
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across
// extensions
std::forward_list<std::string> static_strings; // Stores the std::strings backing
// detail::c_str()
PyTypeObject *static_property_type = nullptr;
PyTypeObject *default_metaclass = nullptr;
PyObject *instance_base = nullptr;
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
thread_specific_storage<PyThreadState> tstate;
thread_specific_storage<loader_life_support> loader_life_support_tls;
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
PyInterpreterState *istate = nullptr;
type_map<PyObject *> native_enum_type_map;
internals()
: static_property_type(make_static_property_type()),
default_metaclass(make_default_metaclass()) {
PyThreadState *cur_tstate = PyThreadState_Get();
tstate = cur_tstate;
istate = cur_tstate->interp;
registered_exception_translators.push_front(&translate_exception);
#ifdef Py_GIL_DISABLED
// Scale proportional to the number of cores. 2x is a heuristic to reduce contention.
// Make sure the number isn't unreasonable by limiting it to 16 bits (65K)
auto num_shards = static_cast<std::uint16_t>(
std::min<std::size_t>(round_up_to_next_pow2(2 * std::thread::hardware_concurrency()),
std::numeric_limits<std::uint16_t>::max()));
if (num_shards == 0) {
num_shards = 1;
}
instance_shards.reset(new instance_map_shard[num_shards]);
instance_shards_mask = num_shards - 1;
#endif
}
internals(const internals &other) = delete;
internals(internals &&other) = delete;
internals &operator=(const internals &other) = delete;
internals &operator=(internals &&other) = delete;
~internals() = default;
};
// the internals struct (above) is shared between all the modules. local_internals are only
// for a single module. Any changes made to internals may require an update to
// PYBIND11_INTERNALS_VERSION, breaking backwards compatibility. local_internals is, by design,
// restricted to a single module. Whether a module has local internals or not should not
// impact any other modules, because the only things accessing the local internals is the
// module that contains them.
struct local_internals {
type_map<type_info *> registered_types_cpp;
std::forward_list<ExceptionTranslator> registered_exception_translators;
PyTypeObject *function_record_py_type = nullptr;
};
enum class holder_enum_t : uint8_t {
undefined,
std_unique_ptr, // Default, lacking interop with std::shared_ptr.
std_shared_ptr, // Lacking interop with std::unique_ptr.
smart_holder, // Full std::unique_ptr / std::shared_ptr interop.
custom_holder,
};
/// Additional type information which does not fit into the PyTypeObject.
/// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`.
struct type_info {
PyTypeObject *type;
const std::type_info *cpptype;
size_t type_size, type_align, holder_size_in_ptrs;
void *(*operator_new)(size_t);
void (*init_instance)(instance *, const void *);
void (*dealloc)(value_and_holder &v_h);
// Cross-DSO-safe function pointers, to sidestep cross-DSO RTTI issues
// on platforms like macOS (see PR #5728 for details):
memory::get_guarded_delete_fn get_memory_guarded_delete = memory::get_guarded_delete;
get_trampoline_self_life_support_fn get_trampoline_self_life_support = nullptr;
std::vector<PyObject *(*) (PyObject *, PyTypeObject *)> implicit_conversions;
std::vector<std::pair<const std::type_info *, void *(*) (void *)>> implicit_casts;
std::vector<bool (*)(PyObject *, void *&)> *direct_conversions;
buffer_info *(*get_buffer)(PyObject *, void *) = nullptr;
void *get_buffer_data = nullptr;
void *(*module_local_load)(PyObject *, const type_info *) = nullptr;
holder_enum_t holder_enum_v = holder_enum_t::undefined;
/* A simple type never occurs as a (direct or indirect) parent
* of a class that makes use of multiple inheritance.
* A type can be simple even if it has non-simple ancestors as long as it has no descendants.
*/
bool simple_type : 1;
/* True if there is no multiple inheritance in this type's inheritance tree */
bool simple_ancestors : 1;
/* true if this is a type registered with py::module_local */
bool module_local : 1;
};
#define PYBIND11_INTERNALS_ID \
"__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
#define PYBIND11_MODULE_LOCAL_ID \
"__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
inline PyThreadState *get_thread_state_unchecked() {
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
return PyThreadState_GET();
#elif PY_VERSION_HEX < 0x030D0000
return _PyThreadState_UncheckedGet();
#else
return PyThreadState_GetUnchecked();
#endif
}
/// We use this counter to figure out if there are or have been multiple subinterpreters active at
/// any point. This must never decrease while any interpreter may be running in any thread!
inline std::atomic<int> &get_num_interpreters_seen() {
static std::atomic<int> counter(0);
return counter;
}
template <class T,
enable_if_t<std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0>
bool handle_nested_exception(const T &exc, const std::exception_ptr &p) {
std::exception_ptr nested = exc.nested_ptr();
if (nested != nullptr && nested != p) {
translate_exception(nested);
return true;
}
return false;
}
template <class T,
enable_if_t<!std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0>
bool handle_nested_exception(const T &exc, const std::exception_ptr &p) {
if (const auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(exc))) {
return handle_nested_exception(*nep, p);
}
return false;
}
inline bool raise_err(PyObject *exc_type, const char *msg) {
if (PyErr_Occurred()) {
raise_from(exc_type, msg);
return true;
}
set_error(exc_type, msg);
return false;
}
inline void translate_exception(std::exception_ptr p) {
if (!p) {
return;
}
try {
std::rethrow_exception(p);
} catch (error_already_set &e) {
handle_nested_exception(e, p);
e.restore();
return;
} catch (const builtin_exception &e) {
// Could not use template since it's an abstract class.
if (const auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(e))) {
handle_nested_exception(*nep, p);
}
e.set_error();
return;
} catch (const std::bad_alloc &e) {
handle_nested_exception(e, p);
raise_err(PyExc_MemoryError, e.what());
return;
} catch (const std::domain_error &e) {
handle_nested_exception(e, p);
raise_err(PyExc_ValueError, e.what());
return;
} catch (const std::invalid_argument &e) {
handle_nested_exception(e, p);
raise_err(PyExc_ValueError, e.what());
return;
} catch (const std::length_error &e) {
handle_nested_exception(e, p);
raise_err(PyExc_ValueError, e.what());
return;
} catch (const std::out_of_range &e) {
handle_nested_exception(e, p);
raise_err(PyExc_IndexError, e.what());
return;
} catch (const std::range_error &e) {
handle_nested_exception(e, p);
raise_err(PyExc_ValueError, e.what());
return;
} catch (const std::overflow_error &e) {
handle_nested_exception(e, p);
raise_err(PyExc_OverflowError, e.what());
return;
} catch (const std::exception &e) {
handle_nested_exception(e, p);
raise_err(PyExc_RuntimeError, e.what());
return;
} catch (const std::nested_exception &e) {
handle_nested_exception(e, p);
raise_err(PyExc_RuntimeError, "Caught an unknown nested exception!");
return;
} catch (...) {
raise_err(PyExc_RuntimeError, "Caught an unknown exception!");
return;
}
}
#if !defined(__GLIBCXX__)
inline void translate_local_exception(std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (error_already_set &e) {
e.restore();
return;
} catch (const builtin_exception &e) {
e.set_error();
return;
}
}
#endif
inline object get_python_state_dict() {
object state_dict;
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
#else
# if PY_VERSION_HEX < 0x03090000
PyInterpreterState *istate = _PyInterpreterState_Get();
# else
PyInterpreterState *istate = PyInterpreterState_Get();
# endif
if (istate) {
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
}
#endif
if (!state_dict) {
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
throw error_already_set();
}
return state_dict;
}
template <typename InternalsType>
class internals_pp_manager {
public:
using on_fetch_function = void(InternalsType *);
internals_pp_manager(char const *id, on_fetch_function *on_fetch)
: holder_id_(id), on_fetch_(on_fetch) {}
/// Get the current pointer-to-pointer, allocating it if it does not already exist. May
/// acquire the GIL. Will never return nullptr.
std::unique_ptr<InternalsType> *get_pp() {
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
if (get_num_interpreters_seen() > 1) {
// Whenever the interpreter changes on the current thread we need to invalidate the
// internals_pp so that it can be pulled from the interpreter's state dict. That is
// slow, so we use the current PyThreadState to check if it is necessary.
auto *tstate = get_thread_state_unchecked();
if (!tstate || tstate->interp != last_istate_.get()) {
gil_scoped_acquire_simple gil;
if (!tstate) {
tstate = get_thread_state_unchecked();
}
last_istate_ = tstate->interp;
internals_tls_p_ = get_or_create_pp_in_state_dict();
}
return internals_tls_p_.get();
}
#endif
if (!internals_singleton_pp_) {
gil_scoped_acquire_simple gil;
internals_singleton_pp_ = get_or_create_pp_in_state_dict();
}
return internals_singleton_pp_;
}
/// Drop all the references we're currently holding.
void unref() {
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
if (get_num_interpreters_seen() > 1) {
last_istate_.reset();
internals_tls_p_.reset();
return;
}
#endif
internals_singleton_pp_ = nullptr;
}
void destroy() {
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
if (get_num_interpreters_seen() > 1) {
auto *tstate = get_thread_state_unchecked();
// this could be called without an active interpreter, just use what was cached
if (!tstate || tstate->interp == last_istate_.get()) {
auto tpp = internals_tls_p_.get();
if (tpp) {
delete tpp;
}
}
unref();
return;
}
#endif
delete internals_singleton_pp_;
unref();
}
private:
std::unique_ptr<InternalsType> *get_or_create_pp_in_state_dict() {
error_scope err_scope;
dict state_dict = get_python_state_dict();
auto internals_obj
= reinterpret_steal<object>(dict_getitemstringref(state_dict.ptr(), holder_id_));
std::unique_ptr<InternalsType> *pp = nullptr;
if (internals_obj) {
void *raw_ptr = PyCapsule_GetPointer(internals_obj.ptr(), /*name=*/nullptr);
if (!raw_ptr) {
raise_from(PyExc_SystemError,
"pybind11::detail::internals_pp_manager::get_pp_from_dict() FAILED");
throw error_already_set();
}
pp = reinterpret_cast<std::unique_ptr<InternalsType> *>(raw_ptr);
if (on_fetch_ && pp) {
on_fetch_(pp->get());
}
} else {
pp = new std::unique_ptr<InternalsType>;
// NOLINTNEXTLINE(bugprone-casting-through-void)
state_dict[holder_id_] = capsule(reinterpret_cast<void *>(pp));
}
return pp;
}
char const *holder_id_ = nullptr;
on_fetch_function *on_fetch_ = nullptr;
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
thread_specific_storage<PyInterpreterState> last_istate_;
thread_specific_storage<std::unique_ptr<InternalsType>> internals_tls_p_;
#endif
std::unique_ptr<InternalsType> *internals_singleton_pp_;
};
// If We loaded the internals through `state_dict`, our `error_already_set`
// and `builtin_exception` may be different local classes than the ones set up in the
// initial exception translator, below, so add another for our local exception classes.
//
// libstdc++ doesn't require this (types there are identified only by name)
// libc++ with CPython doesn't require this (types are explicitly exported)
// libc++ with PyPy still need it, awaiting further investigation
#if !defined(__GLIBCXX__)
inline void check_internals_local_exception_translator(internals *internals_ptr) {
if (internals_ptr) {
for (auto et : internals_ptr->registered_exception_translators) {
if (et == &translate_local_exception) {
return;
}
}
internals_ptr->registered_exception_translators.push_front(&translate_local_exception);
}
}
#endif
inline internals_pp_manager<internals> &get_internals_pp_manager() {
#if defined(__GLIBCXX__)
# define ON_FETCH_FN nullptr
#else
# define ON_FETCH_FN &check_internals_local_exception_translator
#endif
static internals_pp_manager<internals> internals_pp_manager(PYBIND11_INTERNALS_ID,
ON_FETCH_FN);
#undef ON_FETCH_FN
return internals_pp_manager;
}
/// Return a reference to the current `internals` data
PYBIND11_NOINLINE internals &get_internals() {
auto &ppmgr = get_internals_pp_manager();
auto &internals_ptr = *ppmgr.get_pp();
if (!internals_ptr) {
// Slow path, something needs fetched from the state dict or created
gil_scoped_acquire_simple gil;
error_scope err_scope;
internals_ptr.reset(new internals());
if (!internals_ptr->instance_base) {
// This calls get_internals, so cannot be called from within the internals constructor
// called above because internals_ptr must be set before get_internals is called again
internals_ptr->instance_base = make_object_base_type(internals_ptr->default_metaclass);
}
}
return *internals_ptr;
}
inline internals_pp_manager<local_internals> &get_local_internals_pp_manager() {
// Use the address of this static itself as part of the key, so that the value is uniquely tied
// to where the module is loaded in memory
static const std::string this_module_idstr
= PYBIND11_MODULE_LOCAL_ID
+ std::to_string(reinterpret_cast<uintptr_t>(&this_module_idstr));
static internals_pp_manager<local_internals> local_internals_pp_manager(
this_module_idstr.c_str(), nullptr);
return local_internals_pp_manager;
}
/// Works like `get_internals`, but for things which are locally registered.
inline local_internals &get_local_internals() {
auto &ppmgr = get_local_internals_pp_manager();
auto &internals_ptr = *ppmgr.get_pp();
if (!internals_ptr) {
internals_ptr.reset(new local_internals());
}
return *internals_ptr;
}
#ifdef Py_GIL_DISABLED
# define PYBIND11_LOCK_INTERNALS(internals) std::unique_lock<pymutex> lock((internals).mutex)
#else
# define PYBIND11_LOCK_INTERNALS(internals)
#endif
template <typename F>
inline auto with_internals(const F &cb) -> decltype(cb(get_internals())) {
auto &internals = get_internals();
PYBIND11_LOCK_INTERNALS(internals);
return cb(internals);
}
template <typename F>
inline auto with_exception_translators(const F &cb)
-> decltype(cb(get_internals().registered_exception_translators,
get_local_internals().registered_exception_translators)) {
auto &internals = get_internals();
#ifdef Py_GIL_DISABLED
std::unique_lock<pymutex> lock((internals).exception_translator_mutex);
#endif
auto &local_internals = get_local_internals();
return cb(internals.registered_exception_translators,
local_internals.registered_exception_translators);
}
inline std::uint64_t mix64(std::uint64_t z) {
// David Stafford's variant 13 of the MurmurHash3 finalizer popularized
// by the SplitMix PRNG.
// https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}
template <typename F>
inline auto with_instance_map(const void *ptr, const F &cb)
-> decltype(cb(std::declval<instance_map &>())) {
auto &internals = get_internals();
#ifdef Py_GIL_DISABLED
// Hash address to compute shard, but ignore low bits. We'd like allocations
// from the same thread/core to map to the same shard and allocations from
// other threads/cores to map to other shards. Using the high bits is a good
// heuristic because memory allocators often have a per-thread
// arena/superblock/segment from which smaller allocations are served.
auto addr = reinterpret_cast<std::uintptr_t>(ptr);
auto hash = mix64(static_cast<std::uint64_t>(addr >> 20));
auto idx = static_cast<size_t>(hash & internals.instance_shards_mask);
auto &shard = internals.instance_shards[idx];
std::unique_lock<pymutex> lock(shard.mutex);
return cb(shard.registered_instances);
#else
(void) ptr;
return cb(internals.registered_instances);
#endif
}
// Returns the number of registered instances for testing purposes. The result may not be
// consistent if other threads are registering or unregistering instances concurrently.
inline size_t num_registered_instances() {
auto &internals = get_internals();
#ifdef Py_GIL_DISABLED
size_t count = 0;
for (size_t i = 0; i <= internals.instance_shards_mask; ++i) {
auto &shard = internals.instance_shards[i];
std::unique_lock<pymutex> lock(shard.mutex);
count += shard.registered_instances.size();
}
return count;
#else
return internals.registered_instances.size();
#endif
}
/// Constructs a std::string with the given arguments, stores it in `internals`, and returns its
/// `c_str()`. Such strings objects have a long storage duration -- the internal strings are only
/// cleared when the program exits or after interpreter shutdown (when embedding), and so are
/// suitable for c-style strings needed by Python internals (such as PyTypeObject's tp_name).
template <typename... Args>
const char *c_str(Args &&...args) {
// GCC 4.8 doesn't like parameter unpack within lambda capture, so use
// PYBIND11_LOCK_INTERNALS.
auto &internals = get_internals();
PYBIND11_LOCK_INTERNALS(internals);
auto &strings = internals.static_strings;
strings.emplace_front(std::forward<Args>(args)...);
return strings.front().c_str();
}
PYBIND11_NAMESPACE_END(detail)
/// Returns a named pointer that is shared among all extension modules (using the same
/// pybind11 version) running in the current interpreter. Names starting with underscores
/// are reserved for internal usage. Returns `nullptr` if no matching entry was found.
PYBIND11_NOINLINE void *get_shared_data(const std::string &name) {
return detail::with_internals([&](detail::internals &internals) {
auto it = internals.shared_data.find(name);
return it != internals.shared_data.end() ? it->second : nullptr;
});
}
/// Set the shared data that can be later recovered by `get_shared_data()`.
PYBIND11_NOINLINE void *set_shared_data(const std::string &name, void *data) {
return detail::with_internals([&](detail::internals &internals) {
internals.shared_data[name] = data;
return data;
});
}
/// Returns a typed reference to a shared data entry (by using `get_shared_data()`) if
/// such entry exists. Otherwise, a new object of default-constructible type `T` is
/// added to the shared data under the given name and a reference to it is returned.
template <typename T>
T &get_or_create_shared_data(const std::string &name) {
return *detail::with_internals([&](detail::internals &internals) {
auto it = internals.shared_data.find(name);
T *ptr = (T *) (it != internals.shared_data.end() ? it->second : nullptr);
if (!ptr) {
ptr = new T();
internals.shared_data[name] = ptr;
}
return ptr;
});
}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,209 @@
// Copyright (c) 2022-2025 The pybind Community.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "../pytypes.h"
#include "common.h"
#include "internals.h"
#include <cassert>
#include <sstream>
#include <string>
#include <typeindex>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
// This is a separate function only to enable easy unit testing.
inline std::string
native_enum_missing_finalize_error_message(const std::string &enum_name_encoded) {
return "pybind11::native_enum<...>(\"" + enum_name_encoded + "\", ...): MISSING .finalize()";
}
class native_enum_data {
public:
native_enum_data(const object &parent_scope,
const char *enum_name,
const char *native_type_name,
const char *class_doc,
const std::type_index &enum_type_index)
: enum_name_encoded{enum_name}, native_type_name_encoded{native_type_name},
enum_type_index{enum_type_index}, parent_scope(parent_scope), enum_name{enum_name},
native_type_name{native_type_name}, class_doc(class_doc), export_values_flag{false},
finalize_needed{false} {}
void finalize();
native_enum_data(const native_enum_data &) = delete;
native_enum_data &operator=(const native_enum_data &) = delete;
#if !defined(NDEBUG)
// This dtor cannot easily be unit tested because it terminates the process.
~native_enum_data() {
if (finalize_needed) {
pybind11_fail(native_enum_missing_finalize_error_message(enum_name_encoded));
}
}
#endif
protected:
void disarm_finalize_check(const char *error_context) {
if (!finalize_needed) {
pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded
+ "\"): " + error_context);
}
finalize_needed = false;
}
void arm_finalize_check() {
assert(!finalize_needed); // Catch redundant calls.
finalize_needed = true;
}
std::string enum_name_encoded;
std::string native_type_name_encoded;
std::type_index enum_type_index;
private:
object parent_scope;
str enum_name;
str native_type_name;
std::string class_doc;
protected:
list members;
list member_docs;
bool export_values_flag : 1; // Attention: It is best to keep the bools together.
private:
bool finalize_needed : 1;
};
inline void global_internals_native_enum_type_map_set_item(const std::type_index &enum_type_index,
PyObject *py_enum) {
with_internals(
[&](internals &internals) { internals.native_enum_type_map[enum_type_index] = py_enum; });
}
inline handle
global_internals_native_enum_type_map_get_item(const std::type_index &enum_type_index) {
return with_internals([&](internals &internals) {
auto found = internals.native_enum_type_map.find(enum_type_index);
if (found != internals.native_enum_type_map.end()) {
return handle(found->second);
}
return handle();
});
}
inline bool
global_internals_native_enum_type_map_contains(const std::type_index &enum_type_index) {
return with_internals([&](internals &internals) {
return internals.native_enum_type_map.count(enum_type_index) != 0;
});
}
inline object import_or_getattr(const std::string &fully_qualified_name,
const std::string &append_to_exception_message) {
std::istringstream stream(fully_qualified_name);
std::string part;
if (!std::getline(stream, part, '.') || part.empty()) {
std::string msg = "Invalid fully-qualified name `";
msg += fully_qualified_name;
msg += "`";
msg += append_to_exception_message;
throw value_error(msg);
}
auto curr_scope = reinterpret_steal<object>(PyImport_ImportModule(part.c_str()));
if (!curr_scope) {
std::string msg = "Failed to import top-level module `";
msg += part;
msg += "`";
msg += append_to_exception_message;
raise_from(PyExc_ImportError, msg.c_str());
throw error_already_set();
}
// Now recursively getattr or import remaining parts
std::string curr_path = part;
while (std::getline(stream, part, '.')) {
if (part.empty()) {
std::string msg = "Invalid fully-qualified name `";
msg += fully_qualified_name;
msg += "`";
msg += append_to_exception_message;
throw value_error(msg);
}
std::string next_path = curr_path;
next_path += ".";
next_path += part;
auto next_scope
= reinterpret_steal<object>(PyObject_GetAttrString(curr_scope.ptr(), part.c_str()));
if (!next_scope) {
error_fetch_and_normalize stored_getattr_error("getattr");
// Try importing the next level
next_scope = reinterpret_steal<object>(PyImport_ImportModule(next_path.c_str()));
if (!next_scope) {
error_fetch_and_normalize stored_import_error("import");
std::string msg = "Failed to import or getattr `";
msg += part;
msg += "` from `";
msg += curr_path;
msg += "`";
msg += append_to_exception_message;
msg += "\n-------- getattr exception --------\n";
msg += stored_getattr_error.error_string();
msg += "\n-------- import exception --------\n";
msg += stored_import_error.error_string();
throw import_error(msg.c_str());
}
}
curr_scope = next_scope;
curr_path = next_path;
}
return curr_scope;
}
inline void native_enum_data::finalize() {
disarm_finalize_check("DOUBLE finalize");
if (hasattr(parent_scope, enum_name)) {
pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded
+ "\"): an object with that name is already defined");
}
auto py_enum_type = import_or_getattr(native_type_name, " (native_type_name)");
auto py_enum = py_enum_type(enum_name, members);
object module_name = get_module_name_if_available(parent_scope);
if (module_name) {
py_enum.attr("__module__") = module_name;
}
if (hasattr(parent_scope, "__qualname__")) {
const auto parent_qualname = parent_scope.attr("__qualname__").cast<std::string>();
py_enum.attr("__qualname__") = str(parent_qualname + "." + enum_name.cast<std::string>());
}
parent_scope.attr(enum_name) = py_enum;
if (export_values_flag) {
for (auto member : members) {
auto member_name = member[int_(0)];
if (hasattr(parent_scope, member_name)) {
pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded + "\").value(\""
+ member_name.cast<std::string>()
+ "\"): an object with that name is already defined");
}
parent_scope.attr(member_name) = py_enum[member_name];
}
}
if (!class_doc.empty()) {
py_enum.attr("__doc__") = class_doc.c_str();
}
for (auto doc : member_docs) {
py_enum[doc[int_(0)]].attr("__doc__") = doc[int_(1)];
}
global_internals_native_enum_type_map_set_item(enum_type_index, py_enum.release().ptr());
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,82 @@
// Copyright (c) 2016-2025 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
// PLEASE DO NOT ADD ANY INCLUDES HERE
// Define some generic pybind11 helper macros for warning management.
//
// Note that compiler-specific push/pop pairs are baked into the
// PYBIND11_NAMESPACE_BEGIN/PYBIND11_NAMESPACE_END pair of macros. Therefore manual
// PYBIND11_WARNING_PUSH/PYBIND11_WARNING_POP are usually only needed in `#include` sections.
//
// If you find you need to suppress a warning, please try to make the suppression as local as
// possible using these macros. Please also be sure to push/pop with the pybind11 macros. Please
// only use compiler specifics if you need to check specific versions, e.g. Apple Clang vs. vanilla
// Clang.
#if defined(_MSC_VER)
# define PYBIND11_COMPILER_MSVC
# define PYBIND11_PRAGMA(...) __pragma(__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning(push))
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning(pop))
#elif defined(__INTEL_COMPILER)
# define PYBIND11_COMPILER_INTEL
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning pop)
#elif defined(__clang__)
# define PYBIND11_COMPILER_CLANG
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic pop)
#elif defined(__GNUC__)
# define PYBIND11_COMPILER_GCC
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(GCC diagnostic push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(GCC diagnostic pop)
#endif
#ifdef PYBIND11_COMPILER_MSVC
# define PYBIND11_WARNING_DISABLE_MSVC(name) PYBIND11_PRAGMA(warning(disable : name))
#else
# define PYBIND11_WARNING_DISABLE_MSVC(name)
#endif
#ifdef PYBIND11_COMPILER_CLANG
# define PYBIND11_WARNING_DISABLE_CLANG(name) PYBIND11_PRAGMA(clang diagnostic ignored name)
#else
# define PYBIND11_WARNING_DISABLE_CLANG(name)
#endif
#ifdef PYBIND11_COMPILER_GCC
# define PYBIND11_WARNING_DISABLE_GCC(name) PYBIND11_PRAGMA(GCC diagnostic ignored name)
#else
# define PYBIND11_WARNING_DISABLE_GCC(name)
#endif
#ifdef PYBIND11_COMPILER_INTEL
# define PYBIND11_WARNING_DISABLE_INTEL(name) PYBIND11_PRAGMA(warning disable name)
#else
# define PYBIND11_WARNING_DISABLE_INTEL(name)
#endif
#define PYBIND11_NAMESPACE_BEGIN(name) \
namespace name { \
PYBIND11_WARNING_PUSH
#define PYBIND11_NAMESPACE_END(name) \
PYBIND11_WARNING_POP \
}
// Robust support for some features and loading modules compiled against different pybind versions
// requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute
// on the main `pybind11` namespace.
#if !defined(PYBIND11_NAMESPACE)
# if defined(__GNUG__) && !defined(_WIN32)
# define PYBIND11_NAMESPACE pybind11 __attribute__((visibility("hidden")))
# else
# define PYBIND11_NAMESPACE pybind11
# endif
#endif

View File

@@ -0,0 +1,378 @@
// Copyright (c) 2020-2024 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/* Proof-of-Concept for smart pointer interoperability.
High-level aspects:
* Support all `unique_ptr`, `shared_ptr` interops that are feasible.
* Cleanly and clearly report all interops that are infeasible.
* Meant to fit into a `PyObject`, as a holder for C++ objects.
* Support a system design that makes it impossible to trigger
C++ Undefined Behavior, especially from Python.
* Support a system design with clean runtime inheritance casting. From this
it follows that the `smart_holder` needs to be type-erased (`void*`).
* Handling of RTTI for the type-erased held pointer is NOT implemented here.
It is the responsibility of the caller to ensure that `static_cast<T *>`
is well-formed when calling `as_*` member functions. Inheritance casting
needs to be handled in a different layer (similar to the code organization
in boost/python/object/inheritance.hpp).
Details:
* The "root holder" chosen here is a `shared_ptr<void>` (named `vptr` in this
implementation). This choice is practically inevitable because `shared_ptr`
has only very limited support for inspecting and accessing its deleter.
* If created from a raw pointer, or a `unique_ptr` without a custom deleter,
`vptr` always uses a custom deleter, to support `unique_ptr`-like disowning.
The custom deleters could be extended to included life-time management for
external objects (e.g. `PyObject`).
* If created from an external `shared_ptr`, or a `unique_ptr` with a custom
deleter, including life-time management for external objects is infeasible.
* By choice, the smart_holder is movable but not copyable, to keep the design
simple, and to guard against accidental copying overhead.
* The `void_cast_raw_ptr` option is needed to make the `smart_holder` `vptr`
member invisible to the `shared_from_this` mechanism, in case the lifetime
of a `PyObject` is tied to the pointee.
*/
#pragma once
#include "pybind11_namespace_macros.h"
#include <cstring>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <utility>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(memory)
// Default fallback.
static constexpr bool type_has_shared_from_this(...) { return false; }
// This overload uses SFINAE to skip enable_shared_from_this checks when the
// base is inaccessible (e.g. private inheritance).
template <typename T>
static auto type_has_shared_from_this(const T *ptr)
-> decltype(static_cast<const std::enable_shared_from_this<T> *>(ptr), true) {
return true;
}
// Inaccessible base → substitution failure → fallback overload selected
template <typename T>
static constexpr bool type_has_shared_from_this(const void *) {
return false;
}
struct guarded_delete {
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
std::weak_ptr<void> released_ptr; // Trick to keep the smart_holder memory footprint small.
std::function<void(void *)> del_fun; // Rare case.
void (*del_ptr)(void *); // Common case.
bool use_del_fun;
bool armed_flag;
guarded_delete(std::function<void(void *)> &&del_fun, bool armed_flag)
: del_fun{std::move(del_fun)}, del_ptr{nullptr}, use_del_fun{true},
armed_flag{armed_flag} {}
guarded_delete(void (*del_ptr)(void *), bool armed_flag)
: del_ptr{del_ptr}, use_del_fun{false}, armed_flag{armed_flag} {}
void operator()(void *raw_ptr) const {
if (armed_flag) {
if (use_del_fun) {
del_fun(raw_ptr);
} else {
del_ptr(raw_ptr);
}
}
}
};
inline guarded_delete *get_guarded_delete(const std::shared_ptr<void> &ptr) {
return std::get_deleter<guarded_delete>(ptr);
}
using get_guarded_delete_fn = guarded_delete *(*) (const std::shared_ptr<void> &);
template <typename T, typename std::enable_if<std::is_destructible<T>::value, int>::type = 0>
inline void std_default_delete_if_destructible(void *raw_ptr) {
std::default_delete<T>{}(static_cast<T *>(raw_ptr));
}
template <typename T, typename std::enable_if<!std::is_destructible<T>::value, int>::type = 0>
inline void std_default_delete_if_destructible(void *) {
// This noop operator is needed to avoid a compilation error (for `delete raw_ptr;`), but
// throwing an exception from a destructor will std::terminate the process. Therefore the
// runtime check for lifetime-management correctness is implemented elsewhere (in
// ensure_pointee_is_destructible()).
}
template <typename T>
guarded_delete make_guarded_std_default_delete(bool armed_flag) {
return guarded_delete(std_default_delete_if_destructible<T>, armed_flag);
}
template <typename T, typename D>
struct custom_deleter {
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
D deleter;
explicit custom_deleter(D &&deleter) : deleter{std::forward<D>(deleter)} {}
void operator()(void *raw_ptr) { deleter(static_cast<T *>(raw_ptr)); }
};
template <typename T, typename D>
guarded_delete make_guarded_custom_deleter(D &&uqp_del, bool armed_flag) {
return guarded_delete(
std::function<void(void *)>(custom_deleter<T, D>(std::forward<D>(uqp_del))), armed_flag);
}
template <typename T, typename D>
constexpr bool uqp_del_is_std_default_delete() {
return std::is_same<D, std::default_delete<T>>::value
|| std::is_same<D, std::default_delete<T const>>::value;
}
inline bool type_info_equal_across_dso_boundaries(const std::type_info &a,
const std::type_info &b) {
// RTTI pointer comparison may fail across DSOs (e.g., macOS libc++).
// Fallback to name comparison, which is generally safe and ABI-stable enough for our use.
return a == b || std::strcmp(a.name(), b.name()) == 0;
}
struct smart_holder {
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
const std::type_info *rtti_uqp_del = nullptr;
std::shared_ptr<void> vptr;
bool vptr_is_using_noop_deleter : 1;
bool vptr_is_using_std_default_delete : 1;
bool vptr_is_external_shared_ptr : 1;
bool is_populated : 1;
bool is_disowned : 1;
// Design choice: smart_holder is movable but not copyable.
smart_holder(smart_holder &&) = default;
smart_holder(const smart_holder &) = delete;
smart_holder &operator=(smart_holder &&) = delete;
smart_holder &operator=(const smart_holder &) = delete;
smart_holder()
: vptr_is_using_noop_deleter{false}, vptr_is_using_std_default_delete{false},
vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false} {}
bool has_pointee() const { return vptr != nullptr; }
template <typename T>
static void ensure_pointee_is_destructible(const char *context) {
if (!std::is_destructible<T>::value) {
throw std::invalid_argument(std::string("Pointee is not destructible (") + context
+ ").");
}
}
void ensure_is_populated(const char *context) const {
if (!is_populated) {
throw std::runtime_error(std::string("Unpopulated holder (") + context + ").");
}
}
void ensure_is_not_disowned(const char *context) const {
if (is_disowned) {
throw std::runtime_error(std::string("Holder was disowned already (") + context
+ ").");
}
}
void ensure_vptr_is_using_std_default_delete(const char *context) const {
if (vptr_is_external_shared_ptr) {
throw std::invalid_argument(std::string("Cannot disown external shared_ptr (")
+ context + ").");
}
if (vptr_is_using_noop_deleter) {
throw std::invalid_argument(std::string("Cannot disown non-owning holder (") + context
+ ").");
}
if (!vptr_is_using_std_default_delete) {
throw std::invalid_argument(std::string("Cannot disown custom deleter (") + context
+ ").");
}
}
template <typename T, typename D>
void ensure_compatible_uqp_del(const char *context) const {
if (!rtti_uqp_del) {
if (!uqp_del_is_std_default_delete<T, D>()) {
throw std::invalid_argument(std::string("Missing unique_ptr deleter (") + context
+ ").");
}
ensure_vptr_is_using_std_default_delete(context);
return;
}
if (uqp_del_is_std_default_delete<T, D>() && vptr_is_using_std_default_delete) {
return;
}
if (!type_info_equal_across_dso_boundaries(typeid(D), *rtti_uqp_del)) {
throw std::invalid_argument(std::string("Incompatible unique_ptr deleter (") + context
+ ").");
}
}
void ensure_has_pointee(const char *context) const {
if (!has_pointee()) {
throw std::invalid_argument(std::string("Disowned holder (") + context + ").");
}
}
void ensure_use_count_1(const char *context) const {
if (vptr == nullptr) {
throw std::invalid_argument(std::string("Cannot disown nullptr (") + context + ").");
}
// In multithreaded environments accessing use_count can lead to
// race conditions, but in the context of Python it is a bug (elsewhere)
// if the Global Interpreter Lock (GIL) is not being held when this code
// is reached.
// PYBIND11:REMINDER: This may need to be protected by a mutex in free-threaded Python.
if (vptr.use_count() != 1) {
throw std::invalid_argument(std::string("Cannot disown use_count != 1 (") + context
+ ").");
}
}
void reset_vptr_deleter_armed_flag(const get_guarded_delete_fn ggd_fn, bool armed_flag) const {
auto *gd = ggd_fn(vptr);
if (gd == nullptr) {
throw std::runtime_error(
"smart_holder::reset_vptr_deleter_armed_flag() called in an invalid context.");
}
gd->armed_flag = armed_flag;
}
// Caller is responsible for precondition: ensure_compatible_uqp_del<T, D>() must succeed.
template <typename T, typename D>
std::unique_ptr<D> extract_deleter(const char *context,
const get_guarded_delete_fn ggd_fn) const {
auto *gd = ggd_fn(vptr);
if (gd && gd->use_del_fun) {
const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter<T, D>>();
if (custom_deleter_ptr == nullptr) {
throw std::runtime_error(
std::string("smart_holder::extract_deleter() precondition failure (") + context
+ ").");
}
static_assert(std::is_copy_constructible<D>::value,
"Required for compatibility with smart_holder functionality.");
return std::unique_ptr<D>(new D(custom_deleter_ptr->deleter));
}
return nullptr;
}
static smart_holder from_raw_ptr_unowned(void *raw_ptr) {
smart_holder hld;
hld.vptr.reset(raw_ptr, [](void *) {});
hld.vptr_is_using_noop_deleter = true;
hld.is_populated = true;
return hld;
}
template <typename T>
T *as_raw_ptr_unowned() const {
return static_cast<T *>(vptr.get());
}
template <typename T>
static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) {
ensure_pointee_is_destructible<T>("from_raw_ptr_take_ownership");
smart_holder hld;
auto gd = make_guarded_std_default_delete<T>(true);
if (void_cast_raw_ptr) {
hld.vptr.reset(static_cast<void *>(raw_ptr), std::move(gd));
} else {
hld.vptr.reset(raw_ptr, std::move(gd));
}
hld.vptr_is_using_std_default_delete = true;
hld.is_populated = true;
return hld;
}
// Caller is responsible for ensuring the complex preconditions
// (see `smart_holder_type_caster_support::load_helper`).
void disown(const get_guarded_delete_fn ggd_fn) {
reset_vptr_deleter_armed_flag(ggd_fn, false);
is_disowned = true;
}
// Caller is responsible for ensuring the complex preconditions
// (see `smart_holder_type_caster_support::load_helper`).
void reclaim_disowned(const get_guarded_delete_fn ggd_fn) {
reset_vptr_deleter_armed_flag(ggd_fn, true);
is_disowned = false;
}
// Caller is responsible for ensuring the complex preconditions
// (see `smart_holder_type_caster_support::load_helper`).
void release_disowned() { vptr.reset(); }
void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") const {
ensure_is_not_disowned(context);
ensure_vptr_is_using_std_default_delete(context);
ensure_use_count_1(context);
}
// Caller is responsible for ensuring the complex preconditions
// (see `smart_holder_type_caster_support::load_helper`).
void release_ownership(const get_guarded_delete_fn ggd_fn) {
reset_vptr_deleter_armed_flag(ggd_fn, false);
release_disowned();
}
template <typename T, typename D>
static smart_holder from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
void *void_ptr = nullptr) {
smart_holder hld;
hld.rtti_uqp_del = &typeid(D);
hld.vptr_is_using_std_default_delete = uqp_del_is_std_default_delete<T, D>();
guarded_delete gd{nullptr, false};
if (hld.vptr_is_using_std_default_delete) {
gd = make_guarded_std_default_delete<T>(true);
} else {
gd = make_guarded_custom_deleter<T, D>(std::move(unq_ptr.get_deleter()), true);
}
if (void_ptr != nullptr) {
hld.vptr.reset(void_ptr, std::move(gd));
} else {
hld.vptr.reset(unq_ptr.get(), std::move(gd));
}
(void) unq_ptr.release();
hld.is_populated = true;
return hld;
}
template <typename T>
static smart_holder from_shared_ptr(const std::shared_ptr<T> &shd_ptr) {
smart_holder hld;
hld.vptr = std::static_pointer_cast<void>(shd_ptr);
hld.vptr_is_external_shared_ptr = true;
hld.is_populated = true;
return hld;
}
template <typename T>
std::shared_ptr<T> as_shared_ptr() const {
return std::static_pointer_cast<T>(vptr);
}
};
PYBIND11_NAMESPACE_END(memory)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
/*
pybind11/detail/typeid.h: Compiler-independent access to type identifiers
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include <cstdio>
#include <cstdlib>
#if defined(__GNUG__)
# include <cxxabi.h>
#endif
#include "common.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
/// Erase all occurrences of a substring
inline void erase_all(std::string &string, const std::string &search) {
for (size_t pos = 0;;) {
pos = string.find(search, pos);
if (pos == std::string::npos) {
break;
}
string.erase(pos, search.length());
}
}
PYBIND11_NOINLINE void clean_type_id(std::string &name) {
#if defined(__GNUG__)
int status = 0;
std::unique_ptr<char, void (*)(void *)> res{
abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), std::free};
if (status == 0) {
name = res.get();
}
#else
detail::erase_all(name, "class ");
detail::erase_all(name, "struct ");
detail::erase_all(name, "enum ");
#endif
detail::erase_all(name, "pybind11::");
}
inline std::string clean_type_id(const char *typeid_name) {
std::string name(typeid_name);
detail::clean_type_id(name);
return name;
}
PYBIND11_NAMESPACE_END(detail)
/// Return a string representation of a C++ type
template <typename T>
static std::string type_id() {
return detail::clean_type_id(typeid(T).name());
}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2024 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "common.h"
#include "struct_smart_holder.h"
#include <type_traits>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
using pybind11::memory::smart_holder;
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename H>
using is_smart_holder = std::is_same<H, smart_holder>;
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,90 @@
// Copyright (c) 2016-2024 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "common.h"
#include <cstddef>
#include <cstdint>
#include <typeinfo>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
struct value_and_holder {
instance *inst = nullptr;
size_t index = 0u;
const detail::type_info *type = nullptr;
void **vh = nullptr;
// Main constructor for a found value/holder:
value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index)
: inst{i}, index{index}, type{type},
vh{inst->simple_layout ? inst->simple_value_holder
: &inst->nonsimple.values_and_holders[vpos]} {}
// Default constructor (used to signal a value-and-holder not found by get_value_and_holder())
value_and_holder() = default;
// Used for past-the-end iterator
explicit value_and_holder(size_t index) : index{index} {}
template <typename V = void>
V *&value_ptr() const {
return reinterpret_cast<V *&>(vh[0]);
}
// True if this `value_and_holder` has a non-null value pointer
explicit operator bool() const { return value_ptr() != nullptr; }
template <typename H>
H &holder() const {
return reinterpret_cast<H &>(vh[1]);
}
bool holder_constructed() const {
return inst->simple_layout
? inst->simple_holder_constructed
: (inst->nonsimple.status[index] & instance::status_holder_constructed) != 0u;
}
// NOLINTNEXTLINE(readability-make-member-function-const)
void set_holder_constructed(bool v = true) {
if (inst->simple_layout) {
inst->simple_holder_constructed = v;
} else if (v) {
inst->nonsimple.status[index] |= instance::status_holder_constructed;
} else {
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed;
}
}
bool instance_registered() const {
return inst->simple_layout
? inst->simple_instance_registered
: ((inst->nonsimple.status[index] & instance::status_instance_registered) != 0);
}
// NOLINTNEXTLINE(readability-make-member-function-const)
void set_instance_registered(bool v = true) {
if (inst->simple_layout) {
inst->simple_instance_registered = v;
} else if (v) {
inst->nonsimple.status[index] |= instance::status_instance_registered;
} else {
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered;
}
}
};
// This is a semi-public API to check if the corresponding instance has been constructed with a
// holder. That is, if the instance has been constructed with a holder, the `__init__` method is
// called and the C++ object is valid. Otherwise, the C++ object might only be allocated, but not
// initialized. This will lead to **SEGMENTATION FAULTS** if the C++ object is used in any way.
// Example usage: https://pybind11.readthedocs.io/en/stable/advanced/classes.html#custom-type-setup
// for `tp_traverse` and `tp_clear` implementations.
// WARNING: The caller is responsible for ensuring that the `reinterpret_cast` is valid.
inline bool is_holder_constructed(PyObject *obj) {
auto *const instance = reinterpret_cast<pybind11::detail::instance *>(obj);
return instance->get_value_and_holder().holder_constructed();
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,12 @@
/*
pybind11/eigen.h: Transparent conversion for dense and sparse Eigen matrices
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "eigen/matrix.h"

View File

@@ -0,0 +1,9 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
// Common message for `static_assert()`s, which are useful to easily
// preempt much less obvious errors.
#define PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED \
"Pointer types (in particular `PyObject *`) are not supported as scalar types for Eigen " \
"types."

View File

@@ -0,0 +1,723 @@
/*
pybind11/eigen/matrix.h: Transparent conversion for dense and sparse Eigen matrices
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include <pybind11/numpy.h>
#include "common.h"
/* HINT: To suppress warnings originating from the Eigen headers, use -isystem.
See also:
https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir
https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler
*/
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(5054) // https://github.com/pybind/pybind11/pull/3741
// C5054: operator '&': deprecated between enumerations of different types
#if defined(__MINGW32__)
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#endif
#include <Eigen/Core>
#include <Eigen/SparseCore>
PYBIND11_WARNING_POP
// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit
// move constructors that break things. We could detect this an explicitly copy, but an extra copy
// of matrices seems highly undesirable.
static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7),
"Eigen matrix support in pybind11 requires Eigen >= 3.2.7");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides:
using EigenDStride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>;
template <typename MatrixType>
using EigenDRef = Eigen::Ref<MatrixType, 0, EigenDStride>;
template <typename MatrixType>
using EigenDMap = Eigen::Map<MatrixType, 0, EigenDStride>;
PYBIND11_NAMESPACE_BEGIN(detail)
#if EIGEN_VERSION_AT_LEAST(3, 3, 0)
using EigenIndex = Eigen::Index;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::Map<Eigen::SparseMatrix<Scalar, Flags, StorageIndex>>;
#else
using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageIndex>;
#endif
// Matches Eigen::Map, Eigen::Ref, blocks, etc:
template <typename T>
using is_eigen_dense_map = all_of<is_template_base_of<Eigen::DenseBase, T>,
std::is_base_of<Eigen::MapBase<T, Eigen::ReadOnlyAccessors>, T>>;
template <typename T>
using is_eigen_mutable_map = std::is_base_of<Eigen::MapBase<T, Eigen::WriteAccessors>, T>;
template <typename T>
using is_eigen_dense_plain
= all_of<negation<is_eigen_dense_map<T>>, is_template_base_of<Eigen::PlainObjectBase, T>>;
template <typename T>
using is_eigen_sparse = is_template_base_of<Eigen::SparseMatrixBase, T>;
// Test for objects inheriting from EigenBase<Derived> that aren't captured by the above. This
// basically covers anything that can be assigned to a dense matrix but that don't have a typical
// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and
// SelfAdjointView fall into this category.
template <typename T>
using is_eigen_other
= all_of<is_template_base_of<Eigen::EigenBase, T>,
negation<any_of<is_eigen_dense_map<T>, is_eigen_dense_plain<T>, is_eigen_sparse<T>>>>;
// Captures numpy/eigen conformability status (returned by EigenProps::conformable()):
template <bool EigenRowMajor>
struct EigenConformable {
bool conformable = false;
EigenIndex rows = 0, cols = 0;
EigenDStride stride{0, 0}; // Only valid if negativestrides is false!
bool negativestrides = false; // If true, do not use stride!
// NOLINTNEXTLINE(google-explicit-constructor)
EigenConformable(bool fits = false) : conformable{fits} {}
// Matrix type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride)
: conformable{true}, rows{r}, cols{c},
// TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity.
// http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747
stride{EigenRowMajor ? (rstride > 0 ? rstride : 0)
: (cstride > 0 ? cstride : 0) /* outer stride */,
EigenRowMajor ? (cstride > 0 ? cstride : 0)
: (rstride > 0 ? rstride : 0) /* inner stride */},
negativestrides{rstride < 0 || cstride < 0} {}
// Vector type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride)
: EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {}
template <typename props>
bool stride_compatible() const {
// To have compatible strides, we need (on both dimensions) one of fully dynamic strides,
// matching strides, or a dimension size of 1 (in which case the stride value is
// irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant
// (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly).
if (negativestrides) {
return false;
}
if (rows == 0 || cols == 0) {
return true;
}
return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
|| (EigenRowMajor ? cols : rows) == 1)
&& (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer()
|| (EigenRowMajor ? rows : cols) == 1);
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator bool() const { return conformable; }
};
template <typename Type>
struct eigen_extract_stride {
using type = Type;
};
template <typename PlainObjectType, int MapOptions, typename StrideType>
struct eigen_extract_stride<Eigen::Map<PlainObjectType, MapOptions, StrideType>> {
using type = StrideType;
};
template <typename PlainObjectType, int Options, typename StrideType>
struct eigen_extract_stride<Eigen::Ref<PlainObjectType, Options, StrideType>> {
using type = StrideType;
};
// Helper struct for extracting information from an Eigen type
template <typename Type_>
struct EigenProps {
using Type = Type_;
using Scalar = typename Type::Scalar;
using StrideType = typename eigen_extract_stride<Type>::type;
static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime,
size = Type::SizeAtCompileTime;
static constexpr bool row_major = Type::IsRowMajor,
vector
= Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1
fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic,
fixed = size != Eigen::Dynamic, // Fully-fixed size
dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size
template <EigenIndex i, EigenIndex ifzero>
using if_zero = std::integral_constant<EigenIndex, i == 0 ? ifzero : i>;
static constexpr EigenIndex inner_stride
= if_zero<StrideType::InnerStrideAtCompileTime, 1>::value,
outer_stride = if_zero < StrideType::OuterStrideAtCompileTime,
vector ? size
: row_major ? cols
: rows > ::value;
static constexpr bool dynamic_stride
= inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic;
static constexpr bool requires_row_major
= !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1;
static constexpr bool requires_col_major
= !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1;
// Takes an input array and determines whether we can make it fit into the Eigen type. If
// the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector
// (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type).
static EigenConformable<row_major> conformable(const array &a) {
const auto dims = a.ndim();
if (dims < 1 || dims > 2) {
return false;
}
if (dims == 2) { // Matrix type: require exact match (or dynamic)
EigenIndex np_rows = a.shape(0), np_cols = a.shape(1),
np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)),
np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar));
if ((fixed_rows && np_rows != rows) || (fixed_cols && np_cols != cols)) {
return false;
}
return {np_rows, np_cols, np_rstride, np_cstride};
}
// Otherwise we're storing an n-vector. Only one of the strides will be used, but
// whichever is used, we want the (single) numpy stride value.
const EigenIndex n = a.shape(0),
stride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar));
if (vector) { // Eigen type is a compile-time vector
if (fixed && size != n) {
return false; // Vector size mismatch
}
return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride};
}
if (fixed) {
// The type has a fixed size, but is not a vector: abort
return false;
}
if (fixed_cols) {
// Since this isn't a vector, cols must be != 1. We allow this only if it exactly
// equals the number of elements (rows is Dynamic, and so 1 row is allowed).
if (cols != n) {
return false;
}
return {1, n, stride};
} // Otherwise it's either fully dynamic, or column dynamic; both become a column vector
if (fixed_rows && rows != n) {
return false;
}
return {n, 1, stride};
}
static constexpr bool show_writeable
= is_eigen_dense_map<Type>::value && is_eigen_mutable_map<Type>::value;
static constexpr bool show_order = is_eigen_dense_map<Type>::value;
static constexpr bool show_c_contiguous = show_order && requires_row_major;
static constexpr bool show_f_contiguous
= !show_c_contiguous && show_order && requires_col_major;
static constexpr auto descriptor
= const_name("typing.Annotated[")
+ io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
+ npy_format_descriptor<Scalar>::name + io_name("", "]") + const_name(", \"[")
+ const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + const_name(", ")
+ const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n"))
+ const_name("]\"")
// For a reference type (e.g. Ref<MatrixXd>) we have other constraints that might need to
// be satisfied: writeable=True (for a mutable reference), and, depending on the map's
// stride options, possibly f_contiguous or c_contiguous. We include them in the
// descriptor output to provide some hint as to why a TypeError is occurring (otherwise
// it can be confusing to see that a function accepts a
// 'typing.Annotated[numpy.typing.NDArray[numpy.float64], "[3,2]"]' and an error message
// that you *gave* a numpy.ndarray of the right type and dimensions.
+ const_name<show_writeable>(", \"flags.writeable\"", "")
+ const_name<show_c_contiguous>(", \"flags.c_contiguous\"", "")
+ const_name<show_f_contiguous>(", \"flags.f_contiguous\"", "") + const_name("]");
};
// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data,
// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array.
template <typename props>
handle
eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) {
constexpr ssize_t elem_size = sizeof(typename props::Scalar);
array a;
if (props::vector) {
a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base);
} else {
a = array({src.rows(), src.cols()},
{elem_size * src.rowStride(), elem_size * src.colStride()},
src.data(),
base);
}
if (!writeable) {
array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return a.release();
}
// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that
// reference the Eigen object's data with `base` as the python-registered base class (if omitted,
// the base will be set to None, and lifetime management is up to the caller). The numpy array is
// non-writeable if the given type is const.
template <typename props, typename Type>
handle eigen_ref_array(Type &src, handle parent = none()) {
// none here is to get past array's should-we-copy detection, which currently always
// copies when there is no base. Setting the base to None should be harmless.
return eigen_array_cast<props>(src, parent, !std::is_const<Type>::value);
}
// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a
// numpy array that references the encapsulated data with a python-side reference to the capsule to
// tie its destruction to that of any dependent python objects. Const-ness is determined by
// whether or not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) {
capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
return eigen_ref_array<props>(*src, base);
}
// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense
// types.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using props = EigenProps<Type>;
bool load(handle src, bool convert) {
// If we're in no-convert mode, only load if given an array of the correct type
if (!convert && !isinstance<array_t<Scalar>>(src)) {
return false;
}
// Coerce into an array, but don't do type conversion yet; the copy below handles it.
auto buf = array::ensure(src);
if (!buf) {
return false;
}
auto dims = buf.ndim();
if (dims < 1 || dims > 2) {
return false;
}
auto fits = props::conformable(buf);
if (!fits) {
return false;
}
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // See PR #5516
// Allocate the new type, then build a numpy reference into it
value = Type(fits.rows, fits.cols);
PYBIND11_WARNING_POP
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
if (dims == 1) {
ref = ref.squeeze();
} else if (ref.ndim() == 1) {
buf = buf.squeeze();
}
int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr());
if (result < 0) { // Copy failed!
PyErr_Clear();
return false;
}
return true;
}
private:
// Cast implementation
template <typename CType>
static handle cast_impl(CType *src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::take_ownership:
case return_value_policy::automatic:
return eigen_encapsulate<props>(src);
case return_value_policy::move:
return eigen_encapsulate<props>(new CType(std::move(*src)));
case return_value_policy::copy:
return eigen_array_cast<props>(*src);
case return_value_policy::reference:
case return_value_policy::automatic_reference:
return eigen_ref_array<props>(*src);
case return_value_policy::reference_internal:
return eigen_ref_array<props>(*src, parent);
default:
throw cast_error("unhandled return_value_policy: should not happen!");
};
}
public:
// Normal returned non-reference, non-const value:
static handle cast(Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// If you return a non-reference const, we mark the numpy array readonly:
static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// lvalue reference return; default (automatic) becomes copy
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
// const lvalue reference return; default (automatic) becomes copy
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
// non-const pointer return
static handle cast(Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
// const pointer return
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
static constexpr auto name = props::descriptor;
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return &value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &&() && { return std::move(value); }
template <typename T>
using cast_op_type = movable_cast_op_type<T>;
private:
Type value;
};
// Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType>
struct eigen_map_caster {
static_assert(!std::is_pointer<typename MapType::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
private:
using props = EigenProps<MapType>;
public:
// Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has
// to stay around), but we'll allow it under the assumption that you know what you're doing
// (and have an appropriate keep_alive in place). We return a numpy array pointing directly at
// the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.)
// Note that this means you need to ensure you don't destroy the object in some other way (e.g.
// with an appropriate keep_alive, or with a reference to a statically allocated matrix).
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::copy:
return eigen_array_cast<props>(src);
case return_value_policy::reference_internal:
return eigen_array_cast<props>(src, parent, is_eigen_mutable_map<MapType>::value);
case return_value_policy::reference:
case return_value_policy::automatic:
case return_value_policy::automatic_reference:
return eigen_array_cast<props>(src, none(), is_eigen_mutable_map<MapType>::value);
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type");
}
}
// return_descr forces the use of NDArray instead of ArrayLike in args
// since Ref<...> args can only accept arrays.
static constexpr auto name = return_descr(props::descriptor);
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
// types but not bound arguments). We still provide them (with an explicitly delete) so that
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator MapType() = delete;
template <typename>
using cast_op_type = MapType;
};
// We can return any map-like object (but can only load Refs, specialized next):
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_map<Type>::value>> : eigen_map_caster<Type> {};
// Loader for Ref<...> arguments. See the documentation for info on how to make this work without
// copying (it requires some extra effort in many cases).
template <typename PlainObjectType, typename StrideType>
struct type_caster<
Eigen::Ref<PlainObjectType, 0, StrideType>,
enable_if_t<is_eigen_dense_map<Eigen::Ref<PlainObjectType, 0, StrideType>>::value>>
: public eigen_map_caster<Eigen::Ref<PlainObjectType, 0, StrideType>> {
private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>;
using Scalar = typename props::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array
= array_t<Scalar,
array::forcecast
| ((props::row_major ? props::inner_stride : props::outer_stride) == 1
? array::c_style
: (props::row_major ? props::outer_stride : props::inner_stride) == 1
? array::f_style
: 0)>;
static constexpr bool need_writeable = is_eigen_mutable_map<Type>::value;
// Delay construction (these have no default constructor)
std::unique_ptr<MapType> map;
std::unique_ptr<Type> ref;
// Our array. When possible, this is just a numpy array pointing to the source data, but
// sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an
// incompatible layout, or is an array of a type that needs to be converted). Using a numpy
// temporary (rather than an Eigen temporary) saves an extra copy when we need both type
// conversion and storage order conversion. (Note that we refuse to use this temporary copy
// when loading an argument for a Ref<M> with M non-const, i.e. a read-write reference).
Array copy_or_ref;
public:
bool load(handle src, bool convert) {
// First check whether what we have is already an array of the right type. If not, we
// can't avoid a copy (because the copy is also going to do type conversion).
bool need_copy = !isinstance<Array>(src);
EigenConformable<props::row_major> fits;
if (!need_copy) {
// We don't need a converting copy, but we also need to check whether the strides are
// compatible with the Ref's stride requirements
auto aref = reinterpret_borrow<Array>(src);
if (aref && (!need_writeable || aref.writeable())) {
fits = props::conformable(aref);
if (!fits) {
return false; // Incompatible dimensions
}
if (!fits.template stride_compatible<props>()) {
need_copy = true;
} else {
copy_or_ref = std::move(aref);
}
} else {
need_copy = true;
}
}
if (need_copy) {
// We need to copy: If we need a mutable reference, or we're not supposed to convert
// (either because we're in the no-convert overload pass, or because we're explicitly
// instructed not to copy (via `py::arg().noconvert()`) we have to fail loading.
if (!convert || need_writeable) {
return false;
}
Array copy = Array::ensure(src);
if (!copy) {
return false;
}
fits = props::conformable(copy);
if (!fits || !fits.template stride_compatible<props>()) {
return false;
}
copy_or_ref = std::move(copy);
loader_life_support::add_patient(copy_or_ref);
}
ref.reset();
map.reset(new MapType(data(copy_or_ref),
fits.rows,
fits.cols,
make_stride(fits.stride.outer(), fits.stride.inner())));
ref.reset(new Type(*map));
return true;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return ref.get(); }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return *ref; }
template <typename _T>
using cast_op_type = pybind11::detail::cast_op_type<_T>;
private:
template <typename T = Type, enable_if_t<is_eigen_mutable_map<T>::value, int> = 0>
Scalar *data(Array &a) {
return a.mutable_data();
}
template <typename T = Type, enable_if_t<!is_eigen_mutable_map<T>::value, int> = 0>
const Scalar *data(Array &a) {
return a.data();
}
// Attempt to figure out a constructor of `Stride` that will work.
// If both strides are fixed, use a default constructor:
template <typename S>
using stride_ctor_default = bool_constant<S::InnerStrideAtCompileTime != Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_default_constructible<S>::value>;
// Otherwise, if there is a two-index constructor, assume it is (outer,inner) like
// Eigen::Stride, and use it:
template <typename S>
using stride_ctor_dual
= bool_constant<!stride_ctor_default<S>::value
&& std::is_constructible<S, EigenIndex, EigenIndex>::value>;
// Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use
// it (passing whichever stride is dynamic).
template <typename S>
using stride_ctor_outer
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::OuterStrideAtCompileTime == Eigen::Dynamic
&& S::InnerStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S>
using stride_ctor_inner
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::InnerStrideAtCompileTime == Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S = StrideType, enable_if_t<stride_ctor_default<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex) {
return S();
}
template <typename S = StrideType, enable_if_t<stride_ctor_dual<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex inner) {
return S(outer, inner);
}
template <typename S = StrideType, enable_if_t<stride_ctor_outer<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex) {
return S(outer);
}
template <typename S = StrideType, enable_if_t<stride_ctor_inner<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex inner) {
return S(inner);
}
};
// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not
// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout).
// load() is not supported, but we can cast them into the python domain by first copying to a
// regular Eigen::Matrix, then casting that.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
protected:
using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
using props = EigenProps<Matrix>;
public:
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
handle h = eigen_encapsulate<props>(new Matrix(src));
return h;
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast(*src, policy, parent);
}
static constexpr auto name = props::descriptor;
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
// types but not bound arguments). We still provide them (with an explicitly delete) so that
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator Type() = delete;
template <typename>
using cast_op_type = Type;
};
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
using Index = typename Type::Index;
static constexpr bool rowMajor = Type::IsRowMajor;
bool load(handle src, bool) {
if (!src) {
return false;
}
auto obj = reinterpret_borrow<object>(src);
object sparse_module = module_::import("scipy.sparse");
object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix");
if (!type::handle_of(obj).is(matrix_type)) {
try {
obj = matrix_type(obj);
} catch (const error_already_set &) {
return false;
}
}
auto values = array_t<Scalar>((object) obj.attr("data"));
auto innerIndices = array_t<StorageIndex>((object) obj.attr("indices"));
auto outerIndices = array_t<StorageIndex>((object) obj.attr("indptr"));
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
auto nnz = obj.attr("nnz").cast<Index>();
if (!values || !innerIndices || !outerIndices) {
return false;
}
value = EigenMapSparseMatrix<Scalar,
Type::Flags &(Eigen::RowMajor | Eigen::ColMajor),
StorageIndex>(shape[0].cast<Index>(),
shape[1].cast<Index>(),
std::move(nnz),
outerIndices.mutable_data(),
innerIndices.mutable_data(),
values.mutable_data());
return true;
}
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
const_cast<Type &>(src).makeCompressed();
object matrix_type
= module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix");
array data(src.nonZeros(), src.valuePtr());
array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr());
array innerIndices(src.nonZeros(), src.innerIndexPtr());
return matrix_type(pybind11::make_tuple(
std::move(data), std::move(innerIndices), std::move(outerIndices)),
pybind11::make_tuple(src.rows(), src.cols()))
.release();
}
PYBIND11_TYPE_CASTER(Type,
const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[",
"scipy.sparse.csc_matrix[")
+ npy_format_descriptor<Scalar>::name + const_name("]"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,521 @@
/*
pybind11/eigen/tensor.h: Transparent conversion for Eigen tensors
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include <pybind11/numpy.h>
#include "common.h"
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0");
#endif
// Disable warnings for Eigen
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4554)
PYBIND11_WARNING_DISABLE_MSVC(4127)
#if defined(__MINGW32__)
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#endif
#include <unsupported/Eigen/CXX11/Tensor>
PYBIND11_WARNING_POP
static_assert(EIGEN_VERSION_AT_LEAST(3, 3, 0),
"Eigen Tensor support in pybind11 requires Eigen >= 3.3.0");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
inline bool is_tensor_aligned(const void *data) {
return (reinterpret_cast<std::size_t>(data) % EIGEN_DEFAULT_ALIGN_BYTES) == 0;
}
template <typename T>
constexpr int compute_array_flag_from_tensor() {
static_assert((static_cast<int>(T::Layout) == static_cast<int>(Eigen::RowMajor))
|| (static_cast<int>(T::Layout) == static_cast<int>(Eigen::ColMajor)),
"Layout must be row or column major");
return (static_cast<int>(T::Layout) == static_cast<int>(Eigen::RowMajor)) ? array::c_style
: array::f_style;
}
template <typename T>
struct eigen_tensor_helper {};
template <typename Scalar_, int NumIndices_, int Options_, typename IndexType>
struct eigen_tensor_helper<Eigen::Tensor<Scalar_, NumIndices_, Options_, IndexType>> {
using Type = Eigen::Tensor<Scalar_, NumIndices_, Options_, IndexType>;
using ValidType = void;
static Eigen::DSizes<typename Type::Index, Type::NumIndices> get_shape(const Type &f) {
return f.dimensions();
}
static constexpr bool
is_correct_shape(const Eigen::DSizes<typename Type::Index, Type::NumIndices> & /*shape*/) {
return true;
}
template <typename T>
struct helper {};
template <size_t... Is>
struct helper<index_sequence<Is...>> {
static constexpr auto value = ::pybind11::detail::concat(const_name(((void) Is, "?"))...);
};
static constexpr auto dimensions_descriptor
= helper<decltype(make_index_sequence<Type::NumIndices>())>::value;
template <typename... Args>
static Type *alloc(Args &&...args) {
return new Type(std::forward<Args>(args)...);
}
static void free(Type *tensor) { delete tensor; }
};
template <typename Scalar_, typename std::ptrdiff_t... Indices, int Options_, typename IndexType>
struct eigen_tensor_helper<
Eigen::TensorFixedSize<Scalar_, Eigen::Sizes<Indices...>, Options_, IndexType>> {
using Type = Eigen::TensorFixedSize<Scalar_, Eigen::Sizes<Indices...>, Options_, IndexType>;
using ValidType = void;
static constexpr Eigen::DSizes<typename Type::Index, Type::NumIndices>
get_shape(const Type & /*f*/) {
return get_shape();
}
static constexpr Eigen::DSizes<typename Type::Index, Type::NumIndices> get_shape() {
return Eigen::DSizes<typename Type::Index, Type::NumIndices>(Indices...);
}
static bool
is_correct_shape(const Eigen::DSizes<typename Type::Index, Type::NumIndices> &shape) {
return get_shape() == shape;
}
static constexpr auto dimensions_descriptor
= ::pybind11::detail::concat(const_name<Indices>()...);
template <typename... Args>
static Type *alloc(Args &&...args) {
Eigen::aligned_allocator<Type> allocator;
return ::new (allocator.allocate(1)) Type(std::forward<Args>(args)...);
}
static void free(Type *tensor) {
Eigen::aligned_allocator<Type> allocator;
tensor->~Type();
allocator.deallocate(tensor, 1);
}
};
template <typename Type, bool ShowDetails, bool NeedsWriteable = false>
struct get_tensor_descriptor {
static constexpr auto details
= const_name<NeedsWriteable>(", \"flags.writeable\"", "") + const_name
< static_cast<int>(Type::Layout)
== static_cast<int>(Eigen::RowMajor)
> (", \"flags.c_contiguous\"", ", \"flags.f_contiguous\"");
static constexpr auto value
= const_name("typing.Annotated[")
+ io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
+ npy_format_descriptor<typename Type::Scalar>::name + io_name("", "]")
+ const_name(", \"[") + eigen_tensor_helper<remove_cv_t<Type>>::dimensions_descriptor
+ const_name("]\"") + const_name<ShowDetails>(details, const_name("")) + const_name("]");
};
// When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes<T, 0> does not have the begin() member
// function. Falling back to a simple loop works around this issue.
//
// We need to disable the type-limits warning for the inner loop when size = 0.
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wtype-limits")
template <typename T, int size>
std::vector<T> convert_dsizes_to_vector(const Eigen::DSizes<T, size> &arr) {
std::vector<T> result(size);
for (size_t i = 0; i < size; i++) {
result[i] = arr[i];
}
return result;
}
template <typename T, int size>
Eigen::DSizes<T, size> get_shape_for_array(const array &arr) {
Eigen::DSizes<T, size> result;
const T *shape = arr.shape();
for (size_t i = 0; i < size; i++) {
result[i] = shape[i];
}
return result;
}
PYBIND11_WARNING_POP
template <typename Type>
struct type_caster<Type, typename eigen_tensor_helper<Type>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using Helper = eigen_tensor_helper<Type>;
static constexpr auto temp_name = get_tensor_descriptor<Type, false>::value;
PYBIND11_TYPE_CASTER(Type, temp_name);
bool load(handle src, bool convert) {
if (!convert) {
if (!isinstance<array>(src)) {
return false;
}
array temp = array::ensure(src);
if (!temp) {
return false;
}
if (!temp.dtype().is(dtype::of<typename Type::Scalar>())) {
return false;
}
}
array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()> arr(
reinterpret_borrow<object>(src));
if (arr.ndim() != Type::NumIndices) {
return false;
}
auto shape = get_shape_for_array<typename Type::Index, Type::NumIndices>(arr);
if (!Helper::is_correct_shape(shape)) {
return false;
}
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
auto data_pointer = arr.data();
#else
// Handle Eigen bug
auto data_pointer = const_cast<typename Type::Scalar *>(arr.data());
#endif
if (is_tensor_aligned(arr.data())) {
value = Eigen::TensorMap<const Type, Eigen::Aligned>(data_pointer, shape);
} else {
value = Eigen::TensorMap<const Type>(data_pointer, shape);
}
return true;
}
static handle cast(Type &&src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::reference
|| policy == return_value_policy::reference_internal) {
pybind11_fail("Cannot use a reference return value policy for an rvalue");
}
return cast_impl(&src, return_value_policy::move, parent);
}
static handle cast(const Type &&src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::reference
|| policy == return_value_policy::reference_internal) {
pybind11_fail("Cannot use a reference return value policy for an rvalue");
}
return cast_impl(&src, return_value_policy::move, parent);
}
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
static handle cast(Type *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
template <typename C>
static handle cast_impl(C *src, return_value_policy policy, handle parent) {
object parent_object;
bool writeable = false;
switch (policy) {
case return_value_policy::move:
if (std::is_const<C>::value) {
pybind11_fail("Cannot move from a constant reference");
}
src = Helper::alloc(std::move(*src));
parent_object
= capsule(src, [](void *ptr) { Helper::free(reinterpret_cast<Type *>(ptr)); });
writeable = true;
break;
case return_value_policy::take_ownership:
if (std::is_const<C>::value) {
// This cast is ugly, and might be UB in some cases, but we don't have an
// alternative here as we must free that memory
Helper::free(const_cast<Type *>(src));
pybind11_fail("Cannot take ownership of a const reference");
}
parent_object
= capsule(src, [](void *ptr) { Helper::free(reinterpret_cast<Type *>(ptr)); });
writeable = true;
break;
case return_value_policy::copy:
writeable = true;
break;
case return_value_policy::reference:
parent_object = none();
writeable = !std::is_const<C>::value;
break;
case return_value_policy::reference_internal:
// Default should do the right thing
if (!parent) {
pybind11_fail("Cannot use reference internal when there is no parent");
}
parent_object = reinterpret_borrow<object>(parent);
writeable = !std::is_const<C>::value;
break;
default:
pybind11_fail("pybind11 bug in eigen.h, please file a bug report");
}
auto result = array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()>(
convert_dsizes_to_vector(Helper::get_shape(*src)), src->data(), parent_object);
if (!writeable) {
array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return result.release();
}
};
template <typename StoragePointerType,
bool needs_writeable,
enable_if_t<!needs_writeable, bool> = true>
StoragePointerType get_array_data_for_type(array &arr) {
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
return reinterpret_cast<StoragePointerType>(arr.data());
#else
// Handle Eigen bug
return reinterpret_cast<StoragePointerType>(const_cast<void *>(arr.data()));
#endif
}
template <typename StoragePointerType,
bool needs_writeable,
enable_if_t<needs_writeable, bool> = true>
StoragePointerType get_array_data_for_type(array &arr) {
return reinterpret_cast<StoragePointerType>(arr.mutable_data());
}
template <typename T, typename = void>
struct get_storage_pointer_type;
template <typename MapType>
struct get_storage_pointer_type<MapType, void_t<typename MapType::StoragePointerType>> {
using SPT = typename MapType::StoragePointerType;
};
template <typename MapType>
struct get_storage_pointer_type<MapType, void_t<typename MapType::PointerArgType>> {
using SPT = typename MapType::PointerArgType;
};
template <typename Type, int Options>
struct type_caster<Eigen::TensorMap<Type, Options>,
typename eigen_tensor_helper<remove_cv_t<Type>>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::TensorMap<Type, Options>;
using Helper = eigen_tensor_helper<remove_cv_t<Type>>;
bool load(handle src, bool /*convert*/) {
// Note that we have a lot more checks here as we want to make sure to avoid copies
if (!isinstance<array>(src)) {
return false;
}
auto arr = reinterpret_borrow<array>(src);
if ((arr.flags() & compute_array_flag_from_tensor<Type>()) == 0) {
return false;
}
if (!arr.dtype().is(dtype::of<typename Type::Scalar>())) {
return false;
}
if (arr.ndim() != Type::NumIndices) {
return false;
}
constexpr bool is_aligned = (Options & Eigen::Aligned) != 0;
if (is_aligned && !is_tensor_aligned(arr.data())) {
return false;
}
auto shape = get_shape_for_array<typename Type::Index, Type::NumIndices>(arr);
if (!Helper::is_correct_shape(shape)) {
return false;
}
if (needs_writeable && !arr.writeable()) {
return false;
}
auto result = get_array_data_for_type<typename get_storage_pointer_type<MapType>::SPT,
needs_writeable>(arr);
value.reset(new MapType(std::move(result), std::move(shape)));
return true;
}
static handle cast(MapType &&src, return_value_policy policy, handle parent) {
return cast_impl(&src, policy, parent);
}
static handle cast(const MapType &&src, return_value_policy policy, handle parent) {
return cast_impl(&src, policy, parent);
}
static handle cast(MapType &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
static handle cast(MapType *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
static handle cast(const MapType *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
template <typename C>
static handle cast_impl(C *src, return_value_policy policy, handle parent) {
object parent_object;
constexpr bool writeable = !std::is_const<C>::value;
switch (policy) {
case return_value_policy::reference:
parent_object = none();
break;
case return_value_policy::reference_internal:
// Default should do the right thing
if (!parent) {
pybind11_fail("Cannot use reference internal when there is no parent");
}
parent_object = reinterpret_borrow<object>(parent);
break;
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map type, must be either "
"reference or reference_internal");
}
auto result = array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()>(
convert_dsizes_to_vector(Helper::get_shape(*src)),
src->data(),
std::move(parent_object));
if (!writeable) {
array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return result.release();
}
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
static constexpr bool needs_writeable = !std::is_const<typename std::remove_pointer<
typename get_storage_pointer_type<MapType>::SPT>::type>::value;
#else
// Handle Eigen bug
static constexpr bool needs_writeable = !std::is_const<Type>::value;
#endif
protected:
// TODO: Move to std::optional once std::optional has more support
std::unique_ptr<MapType> value;
public:
// return_descr forces the use of NDArray instead of ArrayLike since refs can only reference
// arrays
static constexpr auto name
= return_descr(get_tensor_descriptor<Type, true, needs_writeable>::value);
explicit operator MapType *() { return value.get(); }
explicit operator MapType &() { return *value; }
explicit operator MapType &&() && { return std::move(*value); }
template <typename T_>
using cast_op_type = ::pybind11::detail::movable_cast_op_type<T_>;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,320 @@
/*
pybind11/embed.h: Support for embedding the interpreter
Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include "eval.h"
#include <memory>
#include <vector>
#if defined(PYPY_VERSION)
# error Embedding the interpreter is not supported with PyPy
#endif
#define PYBIND11_EMBEDDED_MODULE_IMPL(name) \
extern "C" PyObject *pybind11_init_impl_##name(); \
extern "C" PyObject *pybind11_init_impl_##name() { return pybind11_init_wrapper_##name(); }
/** \rst
Add a new module to the table of builtins for the interpreter. Must be
defined in global scope. The first macro parameter is the name of the
module (without quotes). The second parameter is the variable which will
be used as the interface to add functions and classes to the module.
.. code-block:: cpp
PYBIND11_EMBEDDED_MODULE(example, m) {
// ... initialize functions and classes here
m.def("foo", []() {
return "Hello, World!";
});
}
The third and subsequent macro arguments are optional, and can be used to
mark the module as supporting various Python features.
- ``mod_gil_not_used()``
- ``multiple_interpreters::per_interpreter_gil()``
- ``multiple_interpreters::shared_gil()``
- ``multiple_interpreters::not_supported()``
.. code-block:: cpp
PYBIND11_EMBEDDED_MODULE(example, m, py::mod_gil_not_used()) {
m.def("foo", []() {
return "Hello, Free-threaded World!";
});
}
\endrst */
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
#define PYBIND11_EMBEDDED_MODULE(name, variable, ...) \
PYBIND11_MODULE_PYINIT(name, {}, ##__VA_ARGS__) \
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)( \
PYBIND11_TOSTRING(name), PYBIND11_CONCAT(PyInit_, name)); \
PYBIND11_MODULE_EXEC(name, variable)
PYBIND11_WARNING_POP
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
/// Python 2.7/3.x compatible version of `PyImport_AppendInittab` and error checks.
struct embedded_module {
using init_t = PyObject *(*) ();
embedded_module(const char *name, init_t init) {
if (Py_IsInitialized() != 0) {
pybind11_fail("Can't add new modules after the interpreter has been initialized");
}
auto result = PyImport_AppendInittab(name, init);
if (result == -1) {
pybind11_fail("Insufficient memory to add a new module");
}
}
};
struct wide_char_arg_deleter {
void operator()(wchar_t *ptr) const {
// API docs: https://docs.python.org/3/c-api/sys.html#c.Py_DecodeLocale
PyMem_RawFree(ptr);
}
};
inline wchar_t *widen_chars(const char *safe_arg) {
wchar_t *widened_arg = Py_DecodeLocale(safe_arg, nullptr);
return widened_arg;
}
inline void precheck_interpreter() {
if (Py_IsInitialized() != 0) {
pybind11_fail("The interpreter is already running");
}
}
#if !defined(PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX)
# define PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX (0x03080000)
#endif
#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers,
int argc,
const char *const *argv,
bool add_program_dir_to_path) {
detail::precheck_interpreter();
Py_InitializeEx(init_signal_handlers ? 1 : 0);
auto argv_size = static_cast<size_t>(argc);
// SetArgv* on python 3 takes wchar_t, so we have to convert.
std::unique_ptr<wchar_t *[]> widened_argv(new wchar_t *[argv_size]);
std::vector<std::unique_ptr<wchar_t[], detail::wide_char_arg_deleter>> widened_argv_entries;
widened_argv_entries.reserve(argv_size);
for (size_t ii = 0; ii < argv_size; ++ii) {
widened_argv_entries.emplace_back(detail::widen_chars(argv[ii]));
if (!widened_argv_entries.back()) {
// A null here indicates a character-encoding failure or the python
// interpreter out of memory. Give up.
return;
}
widened_argv[ii] = widened_argv_entries.back().get();
}
auto *pysys_argv = widened_argv.get();
PySys_SetArgvEx(argc, pysys_argv, static_cast<int>(add_program_dir_to_path));
}
#endif
PYBIND11_NAMESPACE_END(detail)
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
inline void initialize_interpreter(PyConfig *config,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
detail::precheck_interpreter();
PyStatus status = PyConfig_SetBytesArgv(config, argc, const_cast<char *const *>(argv));
if (PyStatus_Exception(status) != 0) {
// A failure here indicates a character-encoding failure or the python
// interpreter out of memory. Give up.
PyConfig_Clear(config);
throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg
: "Failed to prepare CPython");
}
status = Py_InitializeFromConfig(config);
if (PyStatus_Exception(status) != 0) {
PyConfig_Clear(config);
throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg
: "Failed to init CPython");
}
if (add_program_dir_to_path) {
PyRun_SimpleString("import sys, os.path; "
"sys.path.insert(0, "
"os.path.abspath(os.path.dirname(sys.argv[0])) "
"if sys.argv and os.path.exists(sys.argv[0]) else '')");
}
PyConfig_Clear(config);
}
#endif
/** \rst
Initialize the Python interpreter. No other pybind11 or CPython API functions can be
called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The
optional `init_signal_handlers` parameter can be used to skip the registration of
signal handlers (see the `Python documentation`_ for details). Calling this function
again after the interpreter has already been initialized is a fatal error.
If initializing the Python interpreter fails, then the program is terminated. (This
is controlled by the CPython runtime and is an exception to pybind11's normal behavior
of throwing exceptions on errors.)
The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are
used to populate ``sys.argv`` and ``sys.path``.
See the |PySys_SetArgvEx documentation|_ for details.
.. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx
\endrst */
inline void initialize_interpreter(bool init_signal_handlers = true,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
detail::initialize_interpreter_pre_pyconfig(
init_signal_handlers, argc, argv, add_program_dir_to_path);
#else
PyConfig config;
PyConfig_InitPythonConfig(&config);
// See PR #4473 for background
config.parse_argv = 0;
config.install_signal_handlers = init_signal_handlers ? 1 : 0;
initialize_interpreter(&config, argc, argv, add_program_dir_to_path);
#endif
// There is exactly one interpreter alive currently.
detail::get_num_interpreters_seen() = 1;
}
/** \rst
Shut down the Python interpreter. No pybind11 or CPython API functions can be called
after this. In addition, pybind11 objects must not outlive the interpreter:
.. code-block:: cpp
{ // BAD
py::initialize_interpreter();
auto hello = py::str("Hello, World!");
py::finalize_interpreter();
} // <-- BOOM, hello's destructor is called after interpreter shutdown
{ // GOOD
py::initialize_interpreter();
{ // scoped
auto hello = py::str("Hello, World!");
} // <-- OK, hello is cleaned up properly
py::finalize_interpreter();
}
{ // BETTER
py::scoped_interpreter guard{};
auto hello = py::str("Hello, World!");
}
.. warning::
The interpreter can be restarted by calling `initialize_interpreter` again.
Modules created using pybind11 can be safely re-initialized. However, Python
itself cannot completely unload binary extension modules and there are several
caveats with regard to interpreter restarting. All the details can be found
in the CPython documentation. In short, not all interpreter memory may be
freed, either due to reference cycles or user-created global data.
\endrst */
inline void finalize_interpreter() {
// get rid of any thread-local interpreter cache that currently exists
if (detail::get_num_interpreters_seen() > 1) {
detail::get_internals_pp_manager().unref();
detail::get_local_internals_pp_manager().unref();
// We know there can be no other interpreter alive now, so we can lower the count
detail::get_num_interpreters_seen() = 1;
}
// Re-fetch the internals pointer-to-pointer (but not the internals itself, which might not
// exist). It's possible for the internals to be created during Py_Finalize() (e.g. if a
// py::capsule calls `get_internals()` during destruction), so we get the pointer-pointer here
// and check it after Py_Finalize().
detail::get_internals_pp_manager().get_pp();
detail::get_local_internals_pp_manager().get_pp();
Py_Finalize();
detail::get_internals_pp_manager().destroy();
// Local internals contains data managed by the current interpreter, so we must clear them to
// avoid undefined behaviors when initializing another interpreter
detail::get_local_internals_pp_manager().destroy();
// We know there is no interpreter alive now, so we can reset the count
detail::get_num_interpreters_seen() = 0;
}
/** \rst
Scope guard version of `initialize_interpreter` and `finalize_interpreter`.
This a move-only guard and only a single instance can exist.
See `initialize_interpreter` for a discussion of its constructor arguments.
.. code-block:: cpp
#include <pybind11/embed.h>
int main() {
py::scoped_interpreter guard{};
py::print(Hello, World!);
} // <-- interpreter shutdown
\endrst */
class scoped_interpreter {
public:
explicit scoped_interpreter(bool init_signal_handlers = true,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
initialize_interpreter(init_signal_handlers, argc, argv, add_program_dir_to_path);
}
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
explicit scoped_interpreter(PyConfig *config,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
initialize_interpreter(config, argc, argv, add_program_dir_to_path);
}
#endif
scoped_interpreter(const scoped_interpreter &) = delete;
scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; }
scoped_interpreter &operator=(const scoped_interpreter &) = delete;
scoped_interpreter &operator=(scoped_interpreter &&) = delete;
~scoped_interpreter() {
if (is_valid) {
finalize_interpreter();
}
}
private:
bool is_valid = true;
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,161 @@
/*
pybind11/eval.h: Support for evaluating Python expressions and statements
from strings and files
Copyright (c) 2016 Klemens Morgenstern <klemens.morgenstern@ed-chemnitz.de> and
Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include <utility>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
inline void ensure_builtins_in_globals(object &global) {
#if defined(PYPY_VERSION)
// Running exec and eval adds `builtins` module under `__builtins__` key to
// globals if not yet present. Python 3.8 made PyRun_String behave
// similarly. Let's also do that for older versions, for consistency. This
// was missing from PyPy3.8 7.3.7.
if (!global.contains("__builtins__"))
global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE);
#else
(void) global;
#endif
}
PYBIND11_NAMESPACE_END(detail)
enum eval_mode {
/// Evaluate a string containing an isolated expression
eval_expr,
/// Evaluate a string containing a single statement. Returns \c none
eval_single_statement,
/// Evaluate a string containing a sequence of statement. Returns \c none
eval_statements
};
template <eval_mode mode = eval_expr>
object eval(const str &expr, object global = globals(), object local = object()) {
if (!local) {
local = global;
}
detail::ensure_builtins_in_globals(global);
/* PyRun_String does not accept a PyObject / encoding specifier,
this seems to be the only alternative */
std::string buffer = "# -*- coding: utf-8 -*-\n" + (std::string) expr;
int start = 0;
switch (mode) {
case eval_expr:
start = Py_eval_input;
break;
case eval_single_statement:
start = Py_single_input;
break;
case eval_statements:
start = Py_file_input;
break;
default:
pybind11_fail("invalid evaluation mode");
}
PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr());
if (!result) {
throw error_already_set();
}
return reinterpret_steal<object>(result);
}
template <eval_mode mode = eval_expr, size_t N>
object eval(const char (&s)[N], object global = globals(), object local = object()) {
/* Support raw string literals by removing common leading whitespace */
auto expr = (s[0] == '\n') ? str(module_::import("textwrap").attr("dedent")(s)) : str(s);
return eval<mode>(expr, std::move(global), std::move(local));
}
inline void exec(const str &expr, object global = globals(), object local = object()) {
eval<eval_statements>(expr, std::move(global), std::move(local));
}
template <size_t N>
void exec(const char (&s)[N], object global = globals(), object local = object()) {
eval<eval_statements>(s, std::move(global), std::move(local));
}
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
template <eval_mode mode = eval_statements>
object eval_file(str, object, object) {
pybind11_fail("eval_file not supported in this interpreter. Use eval");
}
template <eval_mode mode = eval_statements>
object eval_file(str, object) {
pybind11_fail("eval_file not supported in this interpreter. Use eval");
}
template <eval_mode mode = eval_statements>
object eval_file(str) {
pybind11_fail("eval_file not supported in this interpreter. Use eval");
}
#else
template <eval_mode mode = eval_statements>
object eval_file(str fname, object global = globals(), object local = object()) {
if (!local) {
local = global;
}
detail::ensure_builtins_in_globals(global);
int start = 0;
switch (mode) {
case eval_expr:
start = Py_eval_input;
break;
case eval_single_statement:
start = Py_single_input;
break;
case eval_statements:
start = Py_file_input;
break;
default:
pybind11_fail("invalid evaluation mode");
}
int closeFile = 1;
std::string fname_str = (std::string) fname;
FILE *f =
# if PY_VERSION_HEX >= 0x030E0000
Py_fopen(fname.ptr(), "r");
# else
_Py_fopen_obj(fname.ptr(), "r");
# endif
if (!f) {
PyErr_Clear();
pybind11_fail("File \"" + fname_str + "\" could not be opened!");
}
if (!global.contains("__file__")) {
global["__file__"] = std::move(fname);
}
PyObject *result
= PyRun_FileEx(f, fname_str.c_str(), start, global.ptr(), local.ptr(), closeFile);
if (!result) {
throw error_already_set();
}
return reinterpret_steal<object>(result);
}
#endif
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,147 @@
/*
pybind11/functional.h: std::function<> support
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include <functional>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
PYBIND11_NAMESPACE_BEGIN(type_caster_std_function_specializations)
// ensure GIL is held during functor destruction
struct func_handle {
function f;
#if !(defined(_MSC_VER) && _MSC_VER == 1916 && defined(PYBIND11_CPP17))
// This triggers a syntax error under very special conditions (very weird indeed).
explicit
#endif
func_handle(function &&f_) noexcept
: f(std::move(f_)) {
}
func_handle(const func_handle &f_) { operator=(f_); }
func_handle &operator=(const func_handle &f_) {
gil_scoped_acquire acq;
f = f_.f;
return *this;
}
~func_handle() {
gil_scoped_acquire acq;
function kill_f(std::move(f));
}
};
// to emulate 'move initialization capture' in C++11
struct func_wrapper_base {
func_handle hfunc;
explicit func_wrapper_base(func_handle &&hf) noexcept : hfunc(hf) {}
};
template <typename Return, typename... Args>
struct func_wrapper : func_wrapper_base {
using func_wrapper_base::func_wrapper_base;
Return operator()(Args... args) const { // NOLINT(performance-unnecessary-value-param)
gil_scoped_acquire acq;
// casts the returned object as a rvalue to the return type
return hfunc.f(std::forward<Args>(args)...).template cast<Return>();
}
};
PYBIND11_NAMESPACE_END(type_caster_std_function_specializations)
template <typename Return, typename... Args>
struct type_caster<std::function<Return(Args...)>> {
using type = std::function<Return(Args...)>;
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
using function_type = Return (*)(Args...);
public:
bool load(handle src, bool convert) {
if (src.is_none()) {
// Defer accepting None to other overloads (if we aren't in convert mode):
if (!convert) {
return false;
}
return true;
}
if (!isinstance<function>(src)) {
return false;
}
auto func = reinterpret_borrow<function>(src);
/*
When passing a C++ function as an argument to another C++
function via Python, every function call would normally involve
a full C++ -> Python -> C++ roundtrip, which can be prohibitive.
Here, we try to at least detect the case where the function is
stateless (i.e. function pointer or lambda function without
captured variables), in which case the roundtrip can be avoided.
*/
if (auto cfunc = func.cpp_function()) {
auto *cfunc_self = PyCFunction_GET_SELF(cfunc.ptr());
if (cfunc_self == nullptr) {
PyErr_Clear();
} else {
function_record *rec = function_record_ptr_from_PyObject(cfunc_self);
while (rec != nullptr) {
if (rec->is_stateless
&& same_type(typeid(function_type),
*reinterpret_cast<const std::type_info *>(rec->data[1]))) {
struct capture {
function_type f;
static capture *from_data(void **data) {
return PYBIND11_STD_LAUNDER(reinterpret_cast<capture *>(data));
}
};
PYBIND11_ENSURE_PRECONDITION_FOR_FUNCTIONAL_H_PERFORMANCE_OPTIMIZATIONS(
std::is_standard_layout<capture>::value);
value = capture::from_data(rec->data)->f;
return true;
}
rec = rec->next;
}
}
// PYPY segfaults here when passing builtin function like sum.
// Raising an fail exception here works to prevent the segfault, but only on gcc.
// See PR #1413 for full details
}
value = type_caster_std_function_specializations::func_wrapper<Return, Args...>(
type_caster_std_function_specializations::func_handle(std::move(func)));
return true;
}
template <typename Func>
static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) {
if (!f_) {
return none().release();
}
auto result = f_.template target<function_type>();
if (result) {
return cpp_function(*result, policy).release();
}
return cpp_function(std::forward<Func>(f_), policy).release();
}
PYBIND11_TYPE_CASTER(
type,
const_name("collections.abc.Callable[[")
+ ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...)
+ const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name)
+ const_name("]"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,199 @@
/*
pybind11/gil.h: RAII helpers for managing the GIL
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
# include "detail/common.h"
# include "gil_simple.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
using gil_scoped_acquire = gil_scoped_acquire_simple;
using gil_scoped_release = gil_scoped_release_simple;
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
#else
# include "detail/common.h"
# include "detail/internals.h"
# include <cassert>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wredundant-decls")
// forward declarations
PyThreadState *get_thread_state_unchecked();
PYBIND11_WARNING_POP
PYBIND11_NAMESPACE_END(detail)
/* The functions below essentially reproduce the PyGILState_* API using a RAII
* pattern, but there are a few important differences:
*
* 1. When acquiring the GIL from an non-main thread during the finalization
* phase, the GILState API blindly terminates the calling thread, which
* is often not what is wanted. This API does not do this.
*
* 2. The gil_scoped_release function can optionally cut the relationship
* of a PyThreadState and its associated thread, which allows moving it to
* another thread (this is a fairly rare/advanced use case).
*
* 3. The reference count of an acquired thread state can be controlled. This
* can be handy to prevent cases where callbacks issued from an external
* thread would otherwise constantly construct and destroy thread state data
* structures.
*
* See the Python bindings of NanoGUI (http://github.com/wjakob/nanogui) for an
* example which uses features 2 and 3 to migrate the Python thread of
* execution to another thread (to run the event loop on the original thread,
* in this case).
*/
class gil_scoped_acquire {
public:
PYBIND11_NOINLINE gil_scoped_acquire() {
auto &internals = detail::get_internals();
tstate = internals.tstate.get();
if (!tstate) {
/* Check if the GIL was acquired using the PyGILState_* API instead (e.g. if
calling from a Python thread). Since we use a different key, this ensures
we don't create a new thread state and deadlock in PyEval_AcquireThread
below. Note we don't save this state with internals.tstate, since we don't
create it we would fail to clear it (its reference count should be > 0). */
tstate = PyGILState_GetThisThreadState();
}
if (!tstate) {
tstate = PyThreadState_New(internals.istate);
# if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (!tstate) {
pybind11_fail("scoped_acquire: could not create thread state!");
}
# endif
tstate->gilstate_counter = 0;
internals.tstate = tstate;
} else {
release = detail::get_thread_state_unchecked() != tstate;
}
if (release) {
PyEval_AcquireThread(tstate);
}
inc_ref();
}
gil_scoped_acquire(const gil_scoped_acquire &) = delete;
gil_scoped_acquire &operator=(const gil_scoped_acquire &) = delete;
void inc_ref() { ++tstate->gilstate_counter; }
PYBIND11_NOINLINE void dec_ref() {
--tstate->gilstate_counter;
# if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (detail::get_thread_state_unchecked() != tstate) {
pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!");
}
if (tstate->gilstate_counter < 0) {
pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!");
}
# endif
if (tstate->gilstate_counter == 0) {
# if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (!release) {
pybind11_fail("scoped_acquire::dec_ref(): internal error!");
}
# endif
PyThreadState_Clear(tstate);
if (active) {
PyThreadState_DeleteCurrent();
}
detail::get_internals().tstate.reset();
release = false;
}
}
/// This method will disable the PyThreadState_DeleteCurrent call and the
/// GIL won't be released. This method should be used if the interpreter
/// could be shutting down when this is called, as thread deletion is not
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
/// protect subsequent code.
PYBIND11_NOINLINE void disarm() { active = false; }
PYBIND11_NOINLINE ~gil_scoped_acquire() {
dec_ref();
if (release) {
PyEval_SaveThread();
}
}
private:
PyThreadState *tstate = nullptr;
bool release = true;
bool active = true;
};
class gil_scoped_release {
public:
// PRECONDITION: The GIL must be held when this constructor is called.
explicit gil_scoped_release(bool disassoc = false) : disassoc(disassoc) {
assert(PyGILState_Check());
// `get_internals()` must be called here unconditionally in order to initialize
// `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an
// initialization race could occur as multiple threads try `gil_scoped_acquire`.
auto &internals = detail::get_internals();
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
tstate = PyEval_SaveThread();
if (disassoc) {
internals.tstate.reset();
}
}
gil_scoped_release(const gil_scoped_release &) = delete;
gil_scoped_release &operator=(const gil_scoped_release &) = delete;
/// This method will disable the PyThreadState_DeleteCurrent call and the
/// GIL won't be acquired. This method should be used if the interpreter
/// could be shutting down when this is called, as thread deletion is not
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
/// protect subsequent code.
PYBIND11_NOINLINE void disarm() { active = false; }
~gil_scoped_release() {
if (!tstate) {
return;
}
// `PyEval_RestoreThread()` should not be called if runtime is finalizing
if (active) {
PyEval_RestoreThread(tstate);
}
if (disassoc) {
detail::get_internals().tstate = tstate;
}
}
private:
PyThreadState *tstate;
bool disassoc;
bool active = true;
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
#endif // !PYBIND11_SIMPLE_GIL_MANAGEMENT

View File

@@ -0,0 +1,102 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
#include "detail/common.h"
#include "gil.h"
#include <cassert>
#include <mutex>
#ifdef Py_GIL_DISABLED
# include <atomic>
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
// Use the `gil_safe_call_once_and_store` class below instead of the naive
//
// static auto imported_obj = py::module_::import("module_name"); // BAD, DO NOT USE!
//
// which has two serious issues:
//
// 1. Py_DECREF() calls potentially after the Python interpreter was finalized already, and
// 2. deadlocks in multi-threaded processes (because of missing lock ordering).
//
// The following alternative avoids both problems:
//
// PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
// auto &imported_obj = storage // Do NOT make this `static`!
// .call_once_and_store_result([]() {
// return py::module_::import("module_name");
// })
// .get_stored();
//
// The parameter of `call_once_and_store_result()` must be callable. It can make
// CPython API calls, and in particular, it can temporarily release the GIL.
//
// `T` can be any C++ type, it does not have to involve CPython API types.
//
// The behavior with regard to signals, e.g. `SIGINT` (`KeyboardInterrupt`),
// is not ideal. If the main thread is the one to actually run the `Callable`,
// then a `KeyboardInterrupt` will interrupt it if it is running normal Python
// code. The situation is different if a non-main thread runs the
// `Callable`, and then the main thread starts waiting for it to complete:
// a `KeyboardInterrupt` will not interrupt the non-main thread, but it will
// get processed only when it is the main thread's turn again and it is running
// normal Python code. However, this will be unnoticeable for quick call-once
// functions, which is usually the case.
//
// For in-depth background, see docs/advanced/deadlock.md
template <typename T>
class gil_safe_call_once_and_store {
public:
// PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
template <typename Callable>
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn) {
if (!is_initialized_) { // This read is guarded by the GIL.
// Multiple threads may enter here, because the GIL is released in the next line and
// CPython API calls in the `fn()` call below may release and reacquire the GIL.
gil_scoped_release gil_rel; // Needed to establish lock ordering.
std::call_once(once_flag_, [&] {
// Only one thread will ever enter here.
gil_scoped_acquire gil_acq;
::new (storage_) T(fn()); // fn may release, but will reacquire, the GIL.
is_initialized_ = true; // This write is guarded by the GIL.
});
// All threads will observe `is_initialized_` as true here.
}
// Intentionally not returning `T &` to ensure the calling code is self-documenting.
return *this;
}
// This must only be called after `call_once_and_store_result()` was called.
T &get_stored() {
assert(is_initialized_);
PYBIND11_WARNING_PUSH
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
// Needed for gcc 4.8.5
PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing")
#endif
return *reinterpret_cast<T *>(storage_);
PYBIND11_WARNING_POP
}
constexpr gil_safe_call_once_and_store() = default;
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
private:
alignas(T) char storage_[sizeof(T)] = {};
std::once_flag once_flag_ = {};
#ifdef Py_GIL_DISABLED
std::atomic_bool
#else
bool
#endif
is_initialized_{false};
// The `is_initialized_`-`storage_` pair is very similar to `std::optional`,
// but the latter does not have the triviality properties of former,
// therefore `std::optional` is not a viable alternative here.
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2016-2025 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "detail/common.h"
#include <cassert>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
class gil_scoped_acquire_simple {
PyGILState_STATE state;
public:
gil_scoped_acquire_simple() : state{PyGILState_Ensure()} {}
gil_scoped_acquire_simple(const gil_scoped_acquire_simple &) = delete;
gil_scoped_acquire_simple &operator=(const gil_scoped_acquire_simple &) = delete;
~gil_scoped_acquire_simple() { PyGILState_Release(state); }
};
class gil_scoped_release_simple {
PyThreadState *state;
public:
// PRECONDITION: The GIL must be held when this constructor is called.
gil_scoped_release_simple() {
assert(PyGILState_Check());
state = PyEval_SaveThread();
}
gil_scoped_release_simple(const gil_scoped_release_simple &) = delete;
gil_scoped_release_simple &operator=(const gil_scoped_release_simple &) = delete;
~gil_scoped_release_simple() { PyEval_RestoreThread(state); }
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,265 @@
/*
pybind11/iostream.h -- Tools to assist with redirecting cout and cerr to Python
Copyright (c) 2017 Henry F. Schreiner
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
WARNING: The implementation in this file is NOT thread safe. Multiple
threads writing to a redirected ostream concurrently cause data races
and potentially buffer overflows. Therefore it is currently a requirement
that all (possibly) concurrent redirected ostream writes are protected by
a mutex.
#HelpAppreciated: Work on iostream.h thread safety.
For more background see the discussions under
https://github.com/pybind/pybind11/pull/2982 and
https://github.com/pybind/pybind11/pull/2995.
*/
#pragma once
#include "pybind11.h"
#include <algorithm>
#include <cstring>
#include <iostream>
#include <iterator>
#include <memory>
#include <ostream>
#include <streambuf>
#include <string>
#include <utility>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
// Buffer that writes to Python instead of C++
class pythonbuf : public std::streambuf {
private:
using traits_type = std::streambuf::traits_type;
const size_t buf_size;
std::unique_ptr<char[]> d_buffer;
object pywrite;
object pyflush;
int overflow(int c) override {
if (!traits_type::eq_int_type(c, traits_type::eof())) {
*pptr() = traits_type::to_char_type(c);
pbump(1);
}
return sync() == 0 ? traits_type::not_eof(c) : traits_type::eof();
}
// Computes how many bytes at the end of the buffer are part of an
// incomplete sequence of UTF-8 bytes.
// Precondition: pbase() < pptr()
size_t utf8_remainder() const {
const auto rbase = std::reverse_iterator<char *>(pbase());
const auto rpptr = std::reverse_iterator<char *>(pptr());
auto is_ascii = [](char c) { return (static_cast<unsigned char>(c) & 0x80) == 0x00; };
auto is_leading = [](char c) { return (static_cast<unsigned char>(c) & 0xC0) == 0xC0; };
auto is_leading_2b = [](char c) { return static_cast<unsigned char>(c) <= 0xDF; };
auto is_leading_3b = [](char c) { return static_cast<unsigned char>(c) <= 0xEF; };
// If the last character is ASCII, there are no incomplete code points
if (is_ascii(*rpptr)) {
return 0;
}
// Otherwise, work back from the end of the buffer and find the first
// UTF-8 leading byte
const auto rpend = rbase - rpptr >= 3 ? rpptr + 3 : rbase;
const auto leading = std::find_if(rpptr, rpend, is_leading);
if (leading == rbase) {
return 0;
}
const auto dist = static_cast<size_t>(leading - rpptr);
size_t remainder = 0;
if (dist == 0) {
remainder = 1; // 1-byte code point is impossible
} else if (dist == 1) {
remainder = is_leading_2b(*leading) ? 0 : dist + 1;
} else if (dist == 2) {
remainder = is_leading_3b(*leading) ? 0 : dist + 1;
}
// else if (dist >= 3), at least 4 bytes before encountering an UTF-8
// leading byte, either no remainder or invalid UTF-8.
// Invalid UTF-8 will cause an exception later when converting
// to a Python string, so that's not handled here.
return remainder;
}
// This function must be non-virtual to be called in a destructor.
int _sync() {
if (pbase() != pptr()) { // If buffer is not empty
gil_scoped_acquire tmp;
// This subtraction cannot be negative, so dropping the sign.
auto size = static_cast<size_t>(pptr() - pbase());
size_t remainder = utf8_remainder();
if (size > remainder) {
str line(pbase(), size - remainder);
pywrite(std::move(line));
pyflush();
}
// Copy the remainder at the end of the buffer to the beginning:
if (remainder > 0) {
std::memmove(pbase(), pptr() - remainder, remainder);
}
setp(pbase(), epptr());
pbump(static_cast<int>(remainder));
}
return 0;
}
int sync() override { return _sync(); }
public:
explicit pythonbuf(const object &pyostream, size_t buffer_size = 1024)
: buf_size(buffer_size), d_buffer(new char[buf_size]), pywrite(pyostream.attr("write")),
pyflush(pyostream.attr("flush")) {
setp(d_buffer.get(), d_buffer.get() + buf_size - 1);
}
pythonbuf(pythonbuf &&) = default;
/// Sync before destroy
~pythonbuf() override { _sync(); }
};
PYBIND11_NAMESPACE_END(detail)
/** \rst
This a move-only guard that redirects output.
.. code-block:: cpp
#include <pybind11/iostream.h>
...
{
py::scoped_ostream_redirect output;
std::cout << "Hello, World!"; // Python stdout
} // <-- return std::cout to normal
You can explicitly pass the c++ stream and the python object,
for example to guard stderr instead.
.. code-block:: cpp
{
py::scoped_ostream_redirect output{
std::cerr, py::module::import("sys").attr("stderr")};
std::cout << "Hello, World!";
}
\endrst */
class scoped_ostream_redirect {
protected:
std::streambuf *old;
std::ostream &costream;
detail::pythonbuf buffer;
public:
explicit scoped_ostream_redirect(std::ostream &costream = std::cout,
const object &pyostream
= module_::import("sys").attr("stdout"))
: costream(costream), buffer(pyostream) {
old = costream.rdbuf(&buffer);
}
~scoped_ostream_redirect() { costream.rdbuf(old); }
scoped_ostream_redirect(const scoped_ostream_redirect &) = delete;
scoped_ostream_redirect(scoped_ostream_redirect &&other) = default;
scoped_ostream_redirect &operator=(const scoped_ostream_redirect &) = delete;
scoped_ostream_redirect &operator=(scoped_ostream_redirect &&) = delete;
};
/** \rst
Like `scoped_ostream_redirect`, but redirects cerr by default. This class
is provided primary to make ``py::call_guard`` easier to make.
.. code-block:: cpp
m.def("noisy_func", &noisy_func,
py::call_guard<scoped_ostream_redirect,
scoped_estream_redirect>());
\endrst */
class scoped_estream_redirect : public scoped_ostream_redirect {
public:
explicit scoped_estream_redirect(std::ostream &costream = std::cerr,
const object &pyostream
= module_::import("sys").attr("stderr"))
: scoped_ostream_redirect(costream, pyostream) {}
};
PYBIND11_NAMESPACE_BEGIN(detail)
// Class to redirect output as a context manager. C++ backend.
class OstreamRedirect {
bool do_stdout_;
bool do_stderr_;
std::unique_ptr<scoped_ostream_redirect> redirect_stdout;
std::unique_ptr<scoped_estream_redirect> redirect_stderr;
public:
explicit OstreamRedirect(bool do_stdout = true, bool do_stderr = true)
: do_stdout_(do_stdout), do_stderr_(do_stderr) {}
void enter() {
if (do_stdout_) {
redirect_stdout.reset(new scoped_ostream_redirect());
}
if (do_stderr_) {
redirect_stderr.reset(new scoped_estream_redirect());
}
}
void exit() {
redirect_stdout.reset();
redirect_stderr.reset();
}
};
PYBIND11_NAMESPACE_END(detail)
/** \rst
This is a helper function to add a C++ redirect context manager to Python
instead of using a C++ guard. To use it, add the following to your binding code:
.. code-block:: cpp
#include <pybind11/iostream.h>
...
py::add_ostream_redirect(m, "ostream_redirect");
You now have a Python context manager that redirects your output:
.. code-block:: python
with m.ostream_redirect():
m.print_to_cout_function()
This manager can optionally be told which streams to operate on:
.. code-block:: python
with m.ostream_redirect(stdout=true, stderr=true):
m.noisy_function_with_error_printing()
\endrst */
inline class_<detail::OstreamRedirect>
add_ostream_redirect(module_ m, const std::string &name = "ostream_redirect") {
return class_<detail::OstreamRedirect>(std::move(m), name.c_str(), module_local())
.def(init<bool, bool>(), arg("stdout") = true, arg("stderr") = true)
.def("__enter__", &detail::OstreamRedirect::enter)
.def("__exit__", [](detail::OstreamRedirect &self_, const args &) { self_.exit(); });
}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,67 @@
// Copyright (c) 2022-2025 The pybind Community.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "detail/common.h"
#include "detail/native_enum_data.h"
#include "detail/type_caster_base.h"
#include "cast.h"
#include <cassert>
#include <limits>
#include <type_traits>
#include <typeindex>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/// Conversions between Python's native (stdlib) enum types and C++ enums.
template <typename EnumType>
class native_enum : public detail::native_enum_data {
public:
using Underlying = typename std::underlying_type<EnumType>::type;
native_enum(const object &parent_scope,
const char *name,
const char *native_type_name,
const char *class_doc = "")
: detail::native_enum_data(
parent_scope, name, native_type_name, class_doc, std::type_index(typeid(EnumType))) {
if (detail::get_local_type_info(typeid(EnumType)) != nullptr
|| detail::get_global_type_info(typeid(EnumType)) != nullptr) {
pybind11_fail(
"pybind11::native_enum<...>(\"" + enum_name_encoded
+ "\") is already registered as a `pybind11::enum_` or `pybind11::class_`!");
}
if (detail::global_internals_native_enum_type_map_contains(enum_type_index)) {
pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded
+ "\") is already registered!");
}
arm_finalize_check();
}
/// Export enumeration entries into the parent scope
native_enum &export_values() {
assert(!export_values_flag); // Catch redundant calls.
export_values_flag = true;
return *this;
}
/// Add an enumeration entry
native_enum &value(char const *name, EnumType value, const char *doc = nullptr) {
// Disarm for the case that the native_enum_data dtor runs during exception unwinding.
disarm_finalize_check("value after finalize");
members.append(make_tuple(name, static_cast<Underlying>(value)));
if (doc) {
member_docs.append(make_tuple(name, doc));
}
arm_finalize_check(); // There was no exception.
return *this;
}
native_enum(const native_enum &) = delete;
native_enum &operator=(const native_enum &) = delete;
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,202 @@
/*
pybind11/operator.h: Metatemplates for operator overloading
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
/// Enumeration with all supported operator types
enum op_id : int {
op_add,
op_sub,
op_mul,
op_div,
op_mod,
op_divmod,
op_pow,
op_lshift,
op_rshift,
op_and,
op_xor,
op_or,
op_neg,
op_pos,
op_abs,
op_invert,
op_int,
op_long,
op_float,
op_str,
op_cmp,
op_gt,
op_ge,
op_lt,
op_le,
op_eq,
op_ne,
op_iadd,
op_isub,
op_imul,
op_idiv,
op_imod,
op_ilshift,
op_irshift,
op_iand,
op_ixor,
op_ior,
op_complex,
op_bool,
op_nonzero,
op_repr,
op_truediv,
op_itruediv,
op_hash
};
enum op_type : int {
op_l, /* base type on left */
op_r, /* base type on right */
op_u /* unary operator */
};
struct self_t {};
static const self_t self = self_t();
/// Type for an unused type slot
struct undefined_t {};
/// Don't warn about an unused variable
inline self_t __self() { return self; }
/// base template of operator implementations
template <op_id, op_type, typename B, typename L, typename R>
struct op_impl {};
/// Operator implementation generator
template <op_id id, op_type ot, typename L, typename R>
struct op_ {
static constexpr bool op_enable_if_hook = true;
template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) const {
using Base = typename Class::type;
using L_type = conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute, is_operator(), extra...);
}
template <typename Class, typename... Extra>
void execute_cast(Class &cl, const Extra &...extra) const {
using Base = typename Class::type;
using L_type = conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute_cast, is_operator(), extra...);
}
};
#define PYBIND11_BINARY_OPERATOR(id, rid, op, expr) \
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const *name() { return "__" #id "__"; } \
static auto execute(const L &l, const R &r) -> decltype(expr) { return (expr); } \
static B execute_cast(const L &l, const R &r) { return B(expr); } \
}; \
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_r, B, L, R> { \
static char const *name() { return "__" #rid "__"; } \
static auto execute(const R &r, const L &l) -> decltype(expr) { return (expr); } \
static B execute_cast(const R &r, const L &l) { return B(expr); } \
}; \
inline op_<op_##id, op_l, self_t, self_t> op(const self_t &, const self_t &) { \
return op_<op_##id, op_l, self_t, self_t>(); \
} \
template <typename T> \
op_<op_##id, op_l, self_t, T> op(const self_t &, const T &) { \
return op_<op_##id, op_l, self_t, T>(); \
} \
template <typename T> \
op_<op_##id, op_r, T, self_t> op(const T &, const self_t &) { \
return op_<op_##id, op_r, T, self_t>(); \
}
#define PYBIND11_INPLACE_OPERATOR(id, op, expr) \
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const *name() { return "__" #id "__"; } \
static auto execute(L &l, const R &r) -> decltype(expr) { return expr; } \
static B execute_cast(L &l, const R &r) { return B(expr); } \
}; \
template <typename T> \
op_<op_##id, op_l, self_t, T> op(const self_t &, const T &) { \
return op_<op_##id, op_l, self_t, T>(); \
}
#define PYBIND11_UNARY_OPERATOR(id, op, expr) \
template <typename B, typename L> \
struct op_impl<op_##id, op_u, B, L, undefined_t> { \
static char const *name() { return "__" #id "__"; } \
static auto execute(const L &l) -> decltype(expr) { return expr; } \
static B execute_cast(const L &l) { return B(expr); } \
}; \
inline op_<op_##id, op_u, self_t, undefined_t> op(const self_t &) { \
return op_<op_##id, op_u, self_t, undefined_t>(); \
}
PYBIND11_BINARY_OPERATOR(sub, rsub, operator-, l - r)
PYBIND11_BINARY_OPERATOR(add, radd, operator+, l + r)
PYBIND11_BINARY_OPERATOR(mul, rmul, operator*, l *r)
PYBIND11_BINARY_OPERATOR(truediv, rtruediv, operator/, l / r)
PYBIND11_BINARY_OPERATOR(mod, rmod, operator%, l % r)
PYBIND11_BINARY_OPERATOR(lshift, rlshift, operator<<, l << r)
PYBIND11_BINARY_OPERATOR(rshift, rrshift, operator>>, l >> r)
PYBIND11_BINARY_OPERATOR(and, rand, operator&, l &r)
PYBIND11_BINARY_OPERATOR(xor, rxor, operator^, l ^ r)
PYBIND11_BINARY_OPERATOR(eq, eq, operator==, l == r)
PYBIND11_BINARY_OPERATOR(ne, ne, operator!=, l != r)
PYBIND11_BINARY_OPERATOR(or, ror, operator|, l | r)
PYBIND11_BINARY_OPERATOR(gt, lt, operator>, l > r)
PYBIND11_BINARY_OPERATOR(ge, le, operator>=, l >= r)
PYBIND11_BINARY_OPERATOR(lt, gt, operator<, l < r)
PYBIND11_BINARY_OPERATOR(le, ge, operator<=, l <= r)
// PYBIND11_BINARY_OPERATOR(pow, rpow, pow, std::pow(l, r))
PYBIND11_INPLACE_OPERATOR(iadd, operator+=, l += r)
PYBIND11_INPLACE_OPERATOR(isub, operator-=, l -= r)
PYBIND11_INPLACE_OPERATOR(imul, operator*=, l *= r)
PYBIND11_INPLACE_OPERATOR(itruediv, operator/=, l /= r)
PYBIND11_INPLACE_OPERATOR(imod, operator%=, l %= r)
PYBIND11_INPLACE_OPERATOR(ilshift, operator<<=, l <<= r)
PYBIND11_INPLACE_OPERATOR(irshift, operator>>=, l >>= r)
PYBIND11_INPLACE_OPERATOR(iand, operator&=, l &= r)
PYBIND11_INPLACE_OPERATOR(ixor, operator^=, l ^= r)
PYBIND11_INPLACE_OPERATOR(ior, operator|=, l |= r)
PYBIND11_UNARY_OPERATOR(neg, operator-, -l)
PYBIND11_UNARY_OPERATOR(pos, operator+, +l)
// WARNING: This usage of `abs` should only be done for existing STL overloads.
// Adding overloads directly in to the `std::` namespace is advised against:
// https://en.cppreference.com/w/cpp/language/extending_std
PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l))
PYBIND11_UNARY_OPERATOR(hash, hash, std::hash<L>()(l))
PYBIND11_UNARY_OPERATOR(invert, operator~, (~l))
PYBIND11_UNARY_OPERATOR(bool, operator!, !!l)
PYBIND11_UNARY_OPERATOR(int, int_, (int) l)
PYBIND11_UNARY_OPERATOR(float, float_, (double) l)
#undef PYBIND11_BINARY_OPERATOR
#undef PYBIND11_INPLACE_OPERATOR
#undef PYBIND11_UNARY_OPERATOR
PYBIND11_NAMESPACE_END(detail)
using detail::self;
// Add named operators so that they are accessible via `py::`.
using detail::hash;
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,92 @@
/*
pybind11/options.h: global settings that are configurable at runtime.
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "detail/common.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
class options {
public:
// Default RAII constructor, which leaves settings as they currently are.
options() : previous_state(global_state()) {}
// Class is non-copyable.
options(const options &) = delete;
options &operator=(const options &) = delete;
// Destructor, which restores settings that were in effect before.
~options() { global_state() = previous_state; }
// Setter methods (affect the global state):
options &disable_user_defined_docstrings() & {
global_state().show_user_defined_docstrings = false;
return *this;
}
options &enable_user_defined_docstrings() & {
global_state().show_user_defined_docstrings = true;
return *this;
}
options &disable_function_signatures() & {
global_state().show_function_signatures = false;
return *this;
}
options &enable_function_signatures() & {
global_state().show_function_signatures = true;
return *this;
}
options &disable_enum_members_docstring() & {
global_state().show_enum_members_docstring = false;
return *this;
}
options &enable_enum_members_docstring() & {
global_state().show_enum_members_docstring = true;
return *this;
}
// Getter methods (return the global state):
static bool show_user_defined_docstrings() {
return global_state().show_user_defined_docstrings;
}
static bool show_function_signatures() { return global_state().show_function_signatures; }
static bool show_enum_members_docstring() {
return global_state().show_enum_members_docstring;
}
// This type is not meant to be allocated on the heap.
void *operator new(size_t) = delete;
private:
struct state {
bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings.
bool show_function_signatures = true; //< Include auto-generated function signatures
// in docstrings.
bool show_enum_members_docstring = true; //< Include auto-generated member list in enum
// docstrings.
};
static state &global_state() {
static state instance;
return instance;
}
state previous_state;
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,666 @@
/*
pybind11/stl.h: Transparent conversion for STL data types
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include "detail/common.h"
#include "detail/descr.h"
#include "detail/type_caster_base.h"
#include <deque>
#include <initializer_list>
#include <list>
#include <map>
#include <memory>
#include <ostream>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <valarray>
// See `detail/common.h` for implementation of these guards.
#if defined(PYBIND11_HAS_OPTIONAL)
# include <optional>
#elif defined(PYBIND11_HAS_EXP_OPTIONAL)
# include <experimental/optional>
#endif
#if defined(PYBIND11_HAS_VARIANT)
# include <variant>
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
//
// Begin: Equivalent of
// https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438
/*
The three `object_is_convertible_to_*()` functions below are
the result of converging the behaviors of pybind11 and PyCLIF
(http://github.com/google/clif).
Originally PyCLIF was extremely far on the permissive side of the spectrum,
while pybind11 was very far on the strict side. Originally PyCLIF accepted any
Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as
the elements were convertible. The obvious (in hindsight) problem was that
any empty Python iterable could be passed to any of these C++ types, e.g. `{}`
was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments.
The functions below strike a practical permissive-vs-strict compromise,
informed by tens of thousands of use cases in the wild. A main objective is
to prevent accidents and improve readability:
- Python literals must match the C++ types.
- For C++ `set`: The potentially reducing conversion from a Python sequence
(e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going
through a Python `set`.
- However, a Python `set` can still be passed to a C++ `vector`. The rationale
is that this conversion is not reducing. Implicit conversions of this kind
are also fairly commonly used, therefore enforcing explicit conversions
would have an unfavorable cost : benefit ratio; more sloppily speaking,
such an enforcement would be more annoying than helpful.
Additional checks have been added to allow types derived from `collections.abc.Set` and
`collections.abc.Mapping` (`collections.abc.Sequence` is already allowed by `PySequence_Check`).
*/
inline bool object_is_instance_with_one_of_tp_names(PyObject *obj,
std::initializer_list<const char *> tp_names) {
if (PyType_Check(obj)) {
return false;
}
const char *obj_tp_name = Py_TYPE(obj)->tp_name;
for (const auto *tp_name : tp_names) {
if (std::strcmp(obj_tp_name, tp_name) == 0) {
return true;
}
}
return false;
}
inline bool object_is_convertible_to_std_vector(const handle &src) {
// Allow sequence-like objects, but not (byte-)string-like objects.
if (PySequence_Check(src.ptr()) != 0) {
return !PyUnicode_Check(src.ptr()) && !PyBytes_Check(src.ptr());
}
// Allow generators, set/frozenset and several common iterable types.
return (PyGen_Check(src.ptr()) != 0) || (PyAnySet_Check(src.ptr()) != 0)
|| object_is_instance_with_one_of_tp_names(
src.ptr(), {"dict_keys", "dict_values", "dict_items", "map", "zip"});
}
inline bool object_is_convertible_to_std_set(const handle &src, bool convert) {
// Allow set/frozenset and dict keys.
// In convert mode: also allow types derived from collections.abc.Set.
return ((PyAnySet_Check(src.ptr()) != 0)
|| object_is_instance_with_one_of_tp_names(src.ptr(), {"dict_keys"}))
|| (convert && isinstance(src, module_::import("collections.abc").attr("Set")));
}
inline bool object_is_convertible_to_std_map(const handle &src, bool convert) {
// Allow dict.
if (PyDict_Check(src.ptr())) {
return true;
}
// Allow types conforming to Mapping Protocol.
// According to https://docs.python.org/3/c-api/mapping.html, `PyMappingCheck()` checks for
// `__getitem__()` without checking the type of keys. In order to restrict the allowed types
// closer to actual Mapping-like types, we also check for the `items()` method.
if (PyMapping_Check(src.ptr()) != 0) {
PyObject *items = PyObject_GetAttrString(src.ptr(), "items");
if (items != nullptr) {
bool is_convertible = (PyCallable_Check(items) != 0);
Py_DECREF(items);
if (is_convertible) {
return true;
}
} else {
PyErr_Clear();
}
}
// In convert mode: Allow types derived from collections.abc.Mapping
return convert && isinstance(src, module_::import("collections.abc").attr("Mapping"));
}
//
// End: Equivalent of clif/python/runtime.cc
//
/// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for
/// forwarding a container element). Typically used indirect via forwarded_type(), below.
template <typename T, typename U>
using forwarded_type = conditional_t<std::is_lvalue_reference<T>::value,
remove_reference_t<U> &,
remove_reference_t<U> &&>;
/// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically
/// used for forwarding a container's elements.
template <typename T, typename U>
constexpr forwarded_type<T, U> forward_like(U &&u) {
return std::forward<detail::forwarded_type<T, U>>(std::forward<U>(u));
}
// Checks if a container has a STL style reserve method.
// This will only return true for a `reserve()` with a `void` return.
template <typename C>
using has_reserve_method = std::is_same<decltype(std::declval<C>().reserve(0)), void>;
template <typename Type, typename Key>
struct set_caster {
using type = Type;
using key_conv = make_caster<Key>;
private:
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
void reserve_maybe(const anyset &s, Type *) {
value.reserve(s.size());
}
void reserve_maybe(const anyset &, void *) {}
bool convert_iterable(const iterable &itbl, bool convert) {
for (const auto &it : itbl) {
key_conv conv;
if (!conv.load(it, convert)) {
return false;
}
value.insert(cast_op<Key &&>(std::move(conv)));
}
return true;
}
bool convert_anyset(const anyset &s, bool convert) {
value.clear();
reserve_maybe(s, &value);
return convert_iterable(s, convert);
}
public:
bool load(handle src, bool convert) {
if (!object_is_convertible_to_std_set(src, convert)) {
return false;
}
if (isinstance<anyset>(src)) {
value.clear();
return convert_anyset(reinterpret_borrow<anyset>(src), convert);
}
if (!convert) {
return false;
}
assert(isinstance<iterable>(src));
value.clear();
return convert_iterable(reinterpret_borrow<iterable>(src), convert);
}
template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Key>::policy(policy);
}
pybind11::set s;
for (auto &&value : src) {
auto value_ = reinterpret_steal<object>(
key_conv::cast(detail::forward_like<T>(value), policy, parent));
if (!value_ || !s.add(std::move(value_))) {
return handle();
}
}
return s.release();
}
PYBIND11_TYPE_CASTER(type,
io_name("collections.abc.Set", "set") + const_name("[") + key_conv::name
+ const_name("]"));
};
template <typename Type, typename Key, typename Value>
struct map_caster {
using key_conv = make_caster<Key>;
using value_conv = make_caster<Value>;
private:
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
void reserve_maybe(const dict &d, Type *) {
value.reserve(d.size());
}
void reserve_maybe(const dict &, void *) {}
bool convert_elements(const dict &d, bool convert) {
value.clear();
reserve_maybe(d, &value);
for (const auto &it : d) {
key_conv kconv;
value_conv vconv;
if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) {
return false;
}
value.emplace(cast_op<Key &&>(std::move(kconv)), cast_op<Value &&>(std::move(vconv)));
}
return true;
}
public:
bool load(handle src, bool convert) {
if (!object_is_convertible_to_std_map(src, convert)) {
return false;
}
if (isinstance<dict>(src)) {
return convert_elements(reinterpret_borrow<dict>(src), convert);
}
if (!convert) {
return false;
}
auto items = reinterpret_steal<object>(PyMapping_Items(src.ptr()));
if (!items) {
throw error_already_set();
}
assert(isinstance<iterable>(items));
return convert_elements(dict(reinterpret_borrow<iterable>(items)), convert);
}
template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
dict d;
return_value_policy policy_key = policy;
return_value_policy policy_value = policy;
if (!std::is_lvalue_reference<T>::value) {
policy_key = return_value_policy_override<Key>::policy(policy_key);
policy_value = return_value_policy_override<Value>::policy(policy_value);
}
for (auto &&kv : src) {
auto key = reinterpret_steal<object>(
key_conv::cast(detail::forward_like<T>(kv.first), policy_key, parent));
auto value = reinterpret_steal<object>(
value_conv::cast(detail::forward_like<T>(kv.second), policy_value, parent));
if (!key || !value) {
return handle();
}
d[std::move(key)] = std::move(value);
}
return d.release();
}
PYBIND11_TYPE_CASTER(Type,
io_name("collections.abc.Mapping", "dict") + const_name("[")
+ key_conv::name + const_name(", ") + value_conv::name
+ const_name("]"));
};
template <typename Type, typename Value>
struct list_caster {
using value_conv = make_caster<Value>;
bool load(handle src, bool convert) {
if (!object_is_convertible_to_std_vector(src)) {
return false;
}
if (isinstance<sequence>(src)) {
return convert_elements(src, convert);
}
if (!convert) {
return false;
}
// Designed to be behavior-equivalent to passing tuple(src) from Python:
// The conversion to a tuple will first exhaust the generator object, to ensure that
// the generator is not left in an unpredictable (to the caller) partially-consumed
// state.
assert(isinstance<iterable>(src));
return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert);
}
private:
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
void reserve_maybe(const sequence &s, Type *) {
value.reserve(s.size());
}
void reserve_maybe(const sequence &, void *) {}
bool convert_elements(handle seq, bool convert) {
auto s = reinterpret_borrow<sequence>(seq);
value.clear();
reserve_maybe(s, &value);
for (const auto &it : seq) {
value_conv conv;
if (!conv.load(it, convert)) {
return false;
}
value.push_back(cast_op<Value &&>(std::move(conv)));
}
return true;
}
public:
template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Value>::policy(policy);
}
list l(src.size());
ssize_t index = 0;
for (auto &&value : src) {
auto value_ = reinterpret_steal<object>(
value_conv::cast(detail::forward_like<T>(value), policy, parent));
if (!value_) {
return handle();
}
PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference
}
return l.release();
}
PYBIND11_TYPE_CASTER(Type,
io_name("collections.abc.Sequence", "list") + const_name("[")
+ value_conv::name + const_name("]"));
};
template <typename Type, typename Alloc>
struct type_caster<std::vector<Type, Alloc>> : list_caster<std::vector<Type, Alloc>, Type> {};
template <typename Type, typename Alloc>
struct type_caster<std::deque<Type, Alloc>> : list_caster<std::deque<Type, Alloc>, Type> {};
template <typename Type, typename Alloc>
struct type_caster<std::list<Type, Alloc>> : list_caster<std::list<Type, Alloc>, Type> {};
template <typename ArrayType, typename V, size_t... I>
ArrayType vector_to_array_impl(V &&v, index_sequence<I...>) {
return {{std::move(v[I])...}};
}
// Based on https://en.cppreference.com/w/cpp/container/array/to_array
template <typename ArrayType, size_t N, typename V>
ArrayType vector_to_array(V &&v) {
return vector_to_array_impl<ArrayType, V>(std::forward<V>(v), make_index_sequence<N>{});
}
template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0>
struct array_caster {
using value_conv = make_caster<Value>;
private:
std::unique_ptr<ArrayType> value;
template <bool R = Resizable, enable_if_t<R, int> = 0>
bool convert_elements(handle seq, bool convert) {
auto l = reinterpret_borrow<sequence>(seq);
value.reset(new ArrayType{});
// Using `resize` to preserve the behavior exactly as it was before PR #5305
// For the `resize` to work, `Value` must be default constructible.
// For `std::valarray`, this is a requirement:
// https://en.cppreference.com/w/cpp/named_req/NumericType
value->resize(l.size());
size_t ctr = 0;
for (const auto &it : l) {
value_conv conv;
if (!conv.load(it, convert)) {
return false;
}
(*value)[ctr++] = cast_op<Value &&>(std::move(conv));
}
return true;
}
template <bool R = Resizable, enable_if_t<!R, int> = 0>
bool convert_elements(handle seq, bool convert) {
auto l = reinterpret_borrow<sequence>(seq);
if (l.size() != Size) {
return false;
}
// The `temp` storage is needed to support `Value` types that are not
// default-constructible.
// Deliberate choice: no template specializations, for simplicity, and
// because the compile time overhead for the specializations is deemed
// more significant than the runtime overhead for the `temp` storage.
std::vector<Value> temp;
temp.reserve(l.size());
for (auto it : l) {
value_conv conv;
if (!conv.load(it, convert)) {
return false;
}
temp.emplace_back(cast_op<Value &&>(std::move(conv)));
}
value.reset(new ArrayType(vector_to_array<ArrayType, Size>(std::move(temp))));
return true;
}
public:
bool load(handle src, bool convert) {
if (!object_is_convertible_to_std_vector(src)) {
return false;
}
if (isinstance<sequence>(src)) {
return convert_elements(src, convert);
}
if (!convert) {
return false;
}
// Designed to be behavior-equivalent to passing tuple(src) from Python:
// The conversion to a tuple will first exhaust the generator object, to ensure that
// the generator is not left in an unpredictable (to the caller) partially-consumed
// state.
assert(isinstance<iterable>(src));
return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert);
}
template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
list l(src.size());
ssize_t index = 0;
for (auto &&value : src) {
auto value_ = reinterpret_steal<object>(
value_conv::cast(detail::forward_like<T>(value), policy, parent));
if (!value_) {
return handle();
}
PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference
}
return l.release();
}
// Code copied from PYBIND11_TYPE_CASTER macro.
// Intentionally preserving the behavior exactly as it was before PR #5305
template <typename T_, enable_if_t<std::is_same<ArrayType, remove_cv_t<T_>>::value, int> = 0>
static handle cast(T_ *src, return_value_policy policy, handle parent) {
if (!src) {
return none().release();
}
if (policy == return_value_policy::take_ownership) {
auto h = cast(std::move(*src), policy, parent);
delete src; // WARNING: Assumes `src` was allocated with `new`.
return h;
}
return cast(*src, policy, parent);
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator ArrayType *() { return &(*value); }
// NOLINTNEXTLINE(google-explicit-constructor)
operator ArrayType &() { return *value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator ArrayType &&() && { return std::move(*value); }
template <typename T_>
using cast_op_type = movable_cast_op_type<T_>;
static constexpr auto name
= const_name<Resizable>(const_name(""), const_name("typing.Annotated["))
+ io_name("collections.abc.Sequence", "list") + const_name("[") + value_conv::name
+ const_name("]")
+ const_name<Resizable>(const_name(""),
const_name(", \"FixedSize(") + const_name<Size>()
+ const_name(")\"]"));
};
template <typename Type, size_t Size>
struct type_caster<std::array<Type, Size>>
: array_caster<std::array<Type, Size>, Type, false, Size> {};
template <typename Type>
struct type_caster<std::valarray<Type>> : array_caster<std::valarray<Type>, Type, true> {};
template <typename Key, typename Compare, typename Alloc>
struct type_caster<std::set<Key, Compare, Alloc>>
: set_caster<std::set<Key, Compare, Alloc>, Key> {};
template <typename Key, typename Hash, typename Equal, typename Alloc>
struct type_caster<std::unordered_set<Key, Hash, Equal, Alloc>>
: set_caster<std::unordered_set<Key, Hash, Equal, Alloc>, Key> {};
template <typename Key, typename Value, typename Compare, typename Alloc>
struct type_caster<std::map<Key, Value, Compare, Alloc>>
: map_caster<std::map<Key, Value, Compare, Alloc>, Key, Value> {};
template <typename Key, typename Value, typename Hash, typename Equal, typename Alloc>
struct type_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>>
: map_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>, Key, Value> {};
// This type caster is intended to be used for std::optional and std::experimental::optional
template <typename Type, typename Value = typename Type::value_type>
struct optional_caster {
using value_conv = make_caster<Value>;
template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!src) {
return none().release();
}
if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Value>::policy(policy);
}
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return value_conv::cast(*std::forward<T>(src), policy, parent);
}
bool load(handle src, bool convert) {
if (!src) {
return false;
}
if (src.is_none()) {
return true; // default-constructed value is already empty
}
value_conv inner_caster;
if (!inner_caster.load(src, convert)) {
return false;
}
value.emplace(cast_op<Value &&>(std::move(inner_caster)));
return true;
}
PYBIND11_TYPE_CASTER(Type, value_conv::name | make_caster<none>::name);
};
#if defined(PYBIND11_HAS_OPTIONAL)
template <typename T>
struct type_caster<std::optional<T>> : public optional_caster<std::optional<T>> {};
template <>
struct type_caster<std::nullopt_t> : public void_caster<std::nullopt_t> {};
#endif
#if defined(PYBIND11_HAS_EXP_OPTIONAL)
template <typename T>
struct type_caster<std::experimental::optional<T>>
: public optional_caster<std::experimental::optional<T>> {};
template <>
struct type_caster<std::experimental::nullopt_t>
: public void_caster<std::experimental::nullopt_t> {};
#endif
/// Visit a variant and cast any found type to Python
struct variant_caster_visitor {
return_value_policy policy;
handle parent;
using result_type = handle; // required by boost::variant in C++11
template <typename T>
result_type operator()(T &&src) const {
return make_caster<T>::cast(std::forward<T>(src), policy, parent);
}
};
/// Helper class which abstracts away variant's `visit` function. `std::variant` and similar
/// `namespace::variant` types which provide a `namespace::visit()` function are handled here
/// automatically using argument-dependent lookup. Users can provide specializations for other
/// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`.
template <template <typename...> class Variant>
struct visit_helper {
template <typename... Args>
static auto call(Args &&...args) -> decltype(visit(std::forward<Args>(args)...)) {
return visit(std::forward<Args>(args)...);
}
};
/// Generic variant caster
template <typename Variant>
struct variant_caster;
template <template <typename...> class V, typename... Ts>
struct variant_caster<V<Ts...>> {
static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative.");
template <typename U, typename... Us>
bool load_alternative(handle src, bool convert, type_list<U, Us...>) {
auto caster = make_caster<U>();
if (caster.load(src, convert)) {
value = cast_op<U>(std::move(caster));
return true;
}
return load_alternative(src, convert, type_list<Us...>{});
}
bool load_alternative(handle, bool, type_list<>) { return false; }
bool load(handle src, bool convert) {
// Do a first pass without conversions to improve constructor resolution.
// E.g. `py::int_(1).cast<variant<double, int>>()` needs to fill the `int`
// slot of the variant. Without two-pass loading `double` would be filled
// because it appears first and a conversion is possible.
if (convert && load_alternative(src, false, type_list<Ts...>{})) {
return true;
}
return load_alternative(src, convert, type_list<Ts...>{});
}
template <typename Variant>
static handle cast(Variant &&src, return_value_policy policy, handle parent) {
return visit_helper<V>::call(variant_caster_visitor{policy, parent},
std::forward<Variant>(src));
}
using Type = V<Ts...>;
PYBIND11_TYPE_CASTER(Type, ::pybind11::detail::union_concat(make_caster<Ts>::name...));
};
#if defined(PYBIND11_HAS_VARIANT)
template <typename... Ts>
struct type_caster<std::variant<Ts...>> : variant_caster<std::variant<Ts...>> {};
template <>
struct type_caster<std::monostate> : public void_caster<std::monostate> {};
#endif
PYBIND11_NAMESPACE_END(detail)
inline std::ostream &operator<<(std::ostream &os, const handle &obj) {
#ifdef PYBIND11_HAS_STRING_VIEW
os << str(obj).cast<std::string_view>();
#else
os << (std::string) str(obj);
#endif
return os;
}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2021 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include <pybind11/cast.h>
#include <pybind11/detail/common.h>
#include <pybind11/detail/descr.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <string>
#if defined(PYBIND11_HAS_FILESYSTEM)
# include <filesystem>
#elif defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
# include <experimental/filesystem>
#else
# error "Neither #include <filesystem> nor #include <experimental/filesystem is available."
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
#ifdef PYPY_VERSION
# define PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(...) (__VA_ARGS__)
#else
# define PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(...) \
(reinterpret_cast<void *>(__VA_ARGS__))
#endif
#if defined(PYBIND11_HAS_FILESYSTEM) || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
template <typename T>
struct path_caster {
private:
static PyObject *unicode_from_fs_native(const std::string &w) {
# if !defined(PYPY_VERSION)
return PyUnicode_DecodeFSDefaultAndSize(w.c_str(), ssize_t(w.size()));
# else
// PyPy mistakenly declares the first parameter as non-const.
return PyUnicode_DecodeFSDefaultAndSize(const_cast<char *>(w.c_str()), ssize_t(w.size()));
# endif
}
static PyObject *unicode_from_fs_native(const std::wstring &w) {
return PyUnicode_FromWideChar(w.c_str(), ssize_t(w.size()));
}
public:
static handle cast(const T &path, return_value_policy, handle) {
if (auto py_str = unicode_from_fs_native(path.native())) {
return module_::import("pathlib")
.attr("Path")(reinterpret_steal<object>(py_str))
.release();
}
return nullptr;
}
bool load(handle handle, bool) {
// PyUnicode_FSConverter and PyUnicode_FSDecoder normally take care of
// calling PyOS_FSPath themselves, but that's broken on PyPy (PyPy
// issue #3168) so we do it ourselves instead.
PyObject *buf = PyOS_FSPath(handle.ptr());
if (!buf) {
PyErr_Clear();
return false;
}
PyObject *native = nullptr;
if constexpr (std::is_same_v<typename T::value_type, char>) {
if (PyUnicode_FSConverter(buf, PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(&native))
!= 0) {
if (auto *c_str = PyBytes_AsString(native)) {
// AsString returns a pointer to the internal buffer, which
// must not be free'd.
value = c_str;
}
}
} else if constexpr (std::is_same_v<typename T::value_type, wchar_t>) {
if (PyUnicode_FSDecoder(buf, PYBIND11_REINTERPRET_CAST_VOID_PTR_IF_NOT_PYPY(&native))
!= 0) {
if (auto *c_str = PyUnicode_AsWideCharString(native, nullptr)) {
// AsWideCharString returns a new string that must be free'd.
value = c_str; // Copies the string.
PyMem_Free(c_str);
}
}
}
Py_XDECREF(native);
Py_DECREF(buf);
if (PyErr_Occurred()) {
PyErr_Clear();
return false;
}
return true;
}
PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path"));
};
#endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
#if defined(PYBIND11_HAS_FILESYSTEM)
template <>
struct type_caster<std::filesystem::path> : public path_caster<std::filesystem::path> {};
#elif defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
template <>
struct type_caster<std::experimental::filesystem::path>
: public path_caster<std::experimental::filesystem::path> {};
#endif
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,858 @@
/*
pybind11/std_bind.h: Binding generators for STL data types
Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "detail/common.h"
#include "detail/type_caster_base.h"
#include "cast.h"
#include "operators.h"
#include <algorithm>
#include <sstream>
#include <type_traits>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
/* SFINAE helper class used by 'is_comparable */
template <typename T>
struct container_traits {
template <typename T2>
static std::true_type
test_comparable(decltype(std::declval<const T2 &>() == std::declval<const T2 &>()) *);
template <typename T2>
static std::false_type test_comparable(...);
template <typename T2>
static std::true_type test_value(typename T2::value_type *);
template <typename T2>
static std::false_type test_value(...);
template <typename T2>
static std::true_type test_pair(typename T2::first_type *, typename T2::second_type *);
template <typename T2>
static std::false_type test_pair(...);
static constexpr const bool is_comparable
= std::is_same<std::true_type, decltype(test_comparable<T>(nullptr))>::value;
static constexpr const bool is_pair
= std::is_same<std::true_type, decltype(test_pair<T>(nullptr, nullptr))>::value;
static constexpr const bool is_vector
= std::is_same<std::true_type, decltype(test_value<T>(nullptr))>::value;
static constexpr const bool is_element = !is_pair && !is_vector;
};
/* Default: is_comparable -> std::false_type */
template <typename T, typename SFINAE = void>
struct is_comparable : std::false_type {};
/* For non-map data structures, check whether operator== can be instantiated */
template <typename T>
struct is_comparable<
T,
enable_if_t<container_traits<T>::is_element && container_traits<T>::is_comparable>>
: std::true_type {};
/* For a vector/map data structure, recursively check the value type
(which is std::pair for maps) */
template <typename T>
struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>>
: is_comparable<typename recursive_container_traits<T>::type_to_check_recursively> {};
template <>
struct is_comparable<recursive_bottom> : std::true_type {};
/* For pairs, recursively check the two data types */
template <typename T>
struct is_comparable<T, enable_if_t<container_traits<T>::is_pair>> {
static constexpr const bool value = is_comparable<typename T::first_type>::value
&& is_comparable<typename T::second_type>::value;
};
/* Fallback functions */
template <typename, typename, typename... Args>
void vector_if_copy_constructible(const Args &...) {}
template <typename, typename, typename... Args>
void vector_if_equal_operator(const Args &...) {}
template <typename, typename, typename... Args>
void vector_if_insertion_operator(const Args &...) {}
template <typename, typename, typename... Args>
void vector_modifiers(const Args &...) {}
template <typename Vector, typename Class_>
void vector_if_copy_constructible(enable_if_t<is_copy_constructible<Vector>::value, Class_> &cl) {
cl.def(init<const Vector &>(), "Copy constructor");
}
template <typename Vector, typename Class_>
void vector_if_equal_operator(enable_if_t<is_comparable<Vector>::value, Class_> &cl) {
using T = typename Vector::value_type;
cl.def(self == self);
cl.def(self != self);
cl.def(
"count",
[](const Vector &v, const T &x) { return std::count(v.begin(), v.end(), x); },
arg("x"),
"Return the number of times ``x`` appears in the list");
cl.def(
"remove",
[](Vector &v, const T &x) {
auto p = std::find(v.begin(), v.end(), x);
if (p != v.end()) {
v.erase(p);
} else {
throw value_error();
}
},
arg("x"),
"Remove the first item from the list whose value is x. "
"It is an error if there is no such item.");
cl.def(
"__contains__",
[](const Vector &v, const T &x) { return std::find(v.begin(), v.end(), x) != v.end(); },
arg("x"),
"Return true the container contains ``x``");
}
// Vector modifiers -- requires a copyable vector_type:
// (Technically, some of these (pop and __delitem__) don't actually require copyability, but it
// seems silly to allow deletion but not insertion, so include them here too.)
template <typename Vector, typename Class_>
void vector_modifiers(
enable_if_t<is_copy_constructible<typename Vector::value_type>::value, Class_> &cl) {
using T = typename Vector::value_type;
using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type;
auto wrap_i = [](DiffType i, SizeType n) {
if (i < 0) {
i += n;
}
if (i < 0 || (SizeType) i >= n) {
throw index_error();
}
return i;
};
cl.def(
"append",
[](Vector &v, const T &value) { v.push_back(value); },
arg("x"),
"Add an item to the end of the list");
cl.def(init([](const iterable &it) {
auto v = std::unique_ptr<Vector>(new Vector());
v->reserve(len_hint(it));
for (handle h : it) {
v->push_back(h.cast<T>());
}
return v.release();
}));
cl.def("clear", [](Vector &v) { v.clear(); }, "Clear the contents");
cl.def(
"extend",
[](Vector &v, const Vector &src) { v.insert(v.end(), src.begin(), src.end()); },
arg("L"),
"Extend the list by appending all the items in the given list");
cl.def(
"extend",
[](Vector &v, const iterable &it) {
const size_t old_size = v.size();
v.reserve(old_size + len_hint(it));
try {
for (handle h : it) {
v.push_back(h.cast<T>());
}
} catch (const cast_error &) {
v.erase(v.begin() + static_cast<typename Vector::difference_type>(old_size),
v.end());
try {
v.shrink_to_fit();
} catch (const std::exception &) { // NOLINT(bugprone-empty-catch)
// Do nothing
}
throw;
}
},
arg("L"),
"Extend the list by appending all the items in the given list");
cl.def(
"insert",
[](Vector &v, DiffType i, const T &x) {
// Can't use wrap_i; i == v.size() is OK
if (i < 0) {
i += v.size();
}
if (i < 0 || (SizeType) i > v.size()) {
throw index_error();
}
v.insert(v.begin() + i, x);
},
arg("i"),
arg("x"),
"Insert an item at a given position.");
cl.def(
"pop",
[](Vector &v) {
if (v.empty()) {
throw index_error();
}
T t = std::move(v.back());
v.pop_back();
return t;
},
"Remove and return the last item");
cl.def(
"pop",
[wrap_i](Vector &v, DiffType i) {
i = wrap_i(i, v.size());
T t = std::move(v[(SizeType) i]);
v.erase(std::next(v.begin(), i));
return t;
},
arg("i"),
"Remove and return the item at index ``i``");
cl.def("__setitem__", [wrap_i](Vector &v, DiffType i, const T &t) {
i = wrap_i(i, v.size());
v[(SizeType) i] = t;
});
/// Slicing protocol
cl.def(
"__getitem__",
[](const Vector &v, const slice &slice) -> Vector * {
size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) {
throw error_already_set();
}
auto *seq = new Vector();
seq->reserve((size_t) slicelength);
for (size_t i = 0; i < slicelength; ++i) {
seq->push_back(v[start]);
start += step;
}
return seq;
},
arg("s"),
"Retrieve list elements using a slice object");
cl.def(
"__setitem__",
[](Vector &v, const slice &slice, const Vector &value) {
size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) {
throw error_already_set();
}
if (slicelength != value.size()) {
throw std::runtime_error(
"Left and right hand size of slice assignment have different sizes!");
}
for (size_t i = 0; i < slicelength; ++i) {
v[start] = value[i];
start += step;
}
},
"Assign list elements using a slice object");
cl.def(
"__delitem__",
[wrap_i](Vector &v, DiffType i) {
i = wrap_i(i, v.size());
v.erase(v.begin() + i);
},
"Delete the list elements at index ``i``");
cl.def(
"__delitem__",
[](Vector &v, const slice &slice) {
size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) {
throw error_already_set();
}
if (step == 1 && false) {
v.erase(v.begin() + (DiffType) start, v.begin() + DiffType(start + slicelength));
} else {
for (size_t i = 0; i < slicelength; ++i) {
v.erase(v.begin() + DiffType(start));
start += step - 1;
}
}
},
"Delete list elements using a slice object");
}
// If the type has an operator[] that doesn't return a reference (most notably std::vector<bool>),
// we have to access by copying; otherwise we return by reference.
template <typename Vector>
using vector_needs_copy
= negation<std::is_same<decltype(std::declval<Vector>()[typename Vector::size_type()]),
typename Vector::value_type &>>;
// The usual case: access and iterate by reference
template <typename Vector, typename Class_>
void vector_accessor(enable_if_t<!vector_needs_copy<Vector>::value, Class_> &cl) {
using T = typename Vector::value_type;
using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator;
auto wrap_i = [](DiffType i, SizeType n) {
if (i < 0) {
i += n;
}
if (i < 0 || (SizeType) i >= n) {
throw index_error();
}
return i;
};
cl.def(
"__getitem__",
[wrap_i](Vector &v, DiffType i) -> T & {
i = wrap_i(i, v.size());
return v[(SizeType) i];
},
return_value_policy::reference_internal // ref + keepalive
);
cl.def(
"__iter__",
[](Vector &v) {
return make_iterator<return_value_policy::reference_internal, ItType, ItType, T &>(
v.begin(), v.end());
},
keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
);
}
// The case for special objects, like std::vector<bool>, that have to be returned-by-copy:
template <typename Vector, typename Class_>
void vector_accessor(enable_if_t<vector_needs_copy<Vector>::value, Class_> &cl) {
using T = typename Vector::value_type;
using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator;
cl.def("__getitem__", [](const Vector &v, DiffType i) -> T {
if (i < 0) {
i += v.size();
if (i < 0) {
throw index_error();
}
}
auto i_st = static_cast<SizeType>(i);
if (i_st >= v.size()) {
throw index_error();
}
return v[i_st];
});
cl.def(
"__iter__",
[](Vector &v) {
return make_iterator<return_value_policy::copy, ItType, ItType, T>(v.begin(), v.end());
},
keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
);
}
template <typename Vector, typename Class_>
auto vector_if_insertion_operator(Class_ &cl, std::string const &name)
-> decltype(std::declval<std::ostream &>() << std::declval<typename Vector::value_type>(),
void()) {
using size_type = typename Vector::size_type;
cl.def(
"__repr__",
[name](Vector &v) {
std::ostringstream s;
s << name << '[';
for (size_type i = 0; i < v.size(); ++i) {
s << v[i];
if (i != v.size() - 1) {
s << ", ";
}
}
s << ']';
return s.str();
},
"Return the canonical string representation of this list.");
}
// Provide the buffer interface for vectors if we have data() and we have a format for it
// GCC seems to have "void std::vector<bool>::data()" - doing SFINAE on the existence of data()
// is insufficient, we need to check it returns an appropriate pointer
template <typename Vector, typename = void>
struct vector_has_data_and_format : std::false_type {};
template <typename Vector>
struct vector_has_data_and_format<
Vector,
enable_if_t<std::is_same<decltype(format_descriptor<typename Vector::value_type>::format(),
std::declval<Vector>().data()),
typename Vector::value_type *>::value>> : std::true_type {};
// [workaround(intel)] Separate function required here
// Workaround as the Intel compiler does not compile the enable_if_t part below
// (tested with icc (ICC) 2021.1 Beta 20200827)
template <typename... Args>
constexpr bool args_any_are_buffer() {
return detail::any_of<std::is_same<Args, buffer_protocol>...>::value;
}
// [workaround(intel)] Separate function required here
// [workaround(msvc)] Can't use constexpr bool in return type
// Add the buffer interface to a vector
template <typename Vector, typename Class_, typename... Args>
void vector_buffer_impl(Class_ &cl, std::true_type) {
using T = typename Vector::value_type;
static_assert(vector_has_data_and_format<Vector>::value,
"There is not an appropriate format descriptor for this vector");
// numpy.h declares this for arbitrary types, but it may raise an exception and crash hard
// at runtime if PYBIND11_NUMPY_DTYPE hasn't been called, so check here
format_descriptor<T>::format();
cl.def_buffer([](Vector &v) -> buffer_info {
return buffer_info(v.data(),
static_cast<ssize_t>(sizeof(T)),
format_descriptor<T>::format(),
1,
{v.size()},
{sizeof(T)});
});
cl.def(init([](const buffer &buf) {
auto info = buf.request();
if (info.ndim != 1 || info.strides[0] % static_cast<ssize_t>(sizeof(T))) {
throw type_error("Only valid 1D buffers can be copied to a vector");
}
if (!detail::compare_buffer_info<T>::compare(info)
|| (ssize_t) sizeof(T) != info.itemsize) {
throw type_error("Format mismatch (Python: " + info.format
+ " C++: " + format_descriptor<T>::format() + ")");
}
T *p = static_cast<T *>(info.ptr);
ssize_t step = info.strides[0] / static_cast<ssize_t>(sizeof(T));
T *end = p + info.shape[0] * step;
if (step == 1) {
return Vector(p, end);
}
Vector vec;
vec.reserve((size_t) info.shape[0]);
for (; p != end; p += step) {
vec.push_back(*p);
}
return vec;
}));
return;
}
template <typename Vector, typename Class_, typename... Args>
void vector_buffer_impl(Class_ &, std::false_type) {}
template <typename Vector, typename Class_, typename... Args>
void vector_buffer(Class_ &cl) {
vector_buffer_impl<Vector, Class_, Args...>(
cl, detail::any_of<std::is_same<Args, buffer_protocol>...>{});
}
PYBIND11_NAMESPACE_END(detail)
//
// std::vector
//
template <typename Vector, typename holder_type = default_holder_type<Vector>, typename... Args>
class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args &&...args) {
using Class_ = class_<Vector, holder_type>;
// If the value_type is unregistered (e.g. a converting type) or is itself registered
// module-local then make the vector binding module-local as well:
using vtype = typename Vector::value_type;
auto *vtype_info = detail::get_type_info(typeid(vtype));
bool local = !vtype_info || vtype_info->module_local;
Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
// Declare the buffer interface if a buffer_protocol() is passed in
detail::vector_buffer<Vector, Class_, Args...>(cl);
cl.def(init<>());
// Register copy constructor (if possible)
detail::vector_if_copy_constructible<Vector, Class_>(cl);
// Register comparison-related operators and functions (if possible)
detail::vector_if_equal_operator<Vector, Class_>(cl);
// Register stream insertion operator (if possible)
detail::vector_if_insertion_operator<Vector, Class_>(cl, name);
// Modifiers require copyable vector value type
detail::vector_modifiers<Vector, Class_>(cl);
// Accessor and iterator; return by value if copyable, otherwise we return by ref + keep-alive
detail::vector_accessor<Vector, Class_>(cl);
cl.def(
"__bool__",
[](const Vector &v) -> bool { return !v.empty(); },
"Check whether the list is nonempty");
cl.def("__len__", [](const Vector &vec) { return vec.size(); });
#if 0
// C++ style functions deprecated, leaving it here as an example
cl.def(init<size_type>());
cl.def("resize",
(void (Vector::*) (size_type count)) & Vector::resize,
"changes the number of elements stored");
cl.def("erase",
[](Vector &v, SizeType i) {
if (i >= v.size())
throw index_error();
v.erase(v.begin() + i);
}, "erases element at index ``i``");
cl.def("empty", &Vector::empty, "checks whether the container is empty");
cl.def("size", &Vector::size, "returns the number of elements");
cl.def("push_back", (void (Vector::*)(const T&)) &Vector::push_back, "adds an element to the end");
cl.def("pop_back", &Vector::pop_back, "removes the last element");
cl.def("max_size", &Vector::max_size, "returns the maximum possible number of elements");
cl.def("reserve", &Vector::reserve, "reserves storage");
cl.def("capacity", &Vector::capacity, "returns the number of elements that can be held in currently allocated storage");
cl.def("shrink_to_fit", &Vector::shrink_to_fit, "reduces memory usage by freeing unused memory");
cl.def("clear", &Vector::clear, "clears the contents");
cl.def("swap", &Vector::swap, "swaps the contents");
cl.def("front", [](Vector &v) {
if (v.size()) return v.front();
else throw index_error();
}, "access the first element");
cl.def("back", [](Vector &v) {
if (v.size()) return v.back();
else throw index_error();
}, "access the last element ");
#endif
return cl;
}
//
// std::map, std::unordered_map
//
PYBIND11_NAMESPACE_BEGIN(detail)
/* Fallback functions */
template <typename, typename, typename... Args>
void map_if_insertion_operator(const Args &...) {}
template <typename, typename, typename... Args>
void map_assignment(const Args &...) {}
// Map assignment when copy-assignable: just copy the value
template <typename Map, typename Class_>
void map_assignment(
enable_if_t<is_copy_assignable<typename Map::mapped_type>::value, Class_> &cl) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
cl.def("__setitem__", [](Map &m, const KeyType &k, const MappedType &v) {
auto it = m.find(k);
if (it != m.end()) {
it->second = v;
} else {
m.emplace(k, v);
}
});
}
// Not copy-assignable, but still copy-constructible: we can update the value by erasing and
// reinserting
template <typename Map, typename Class_>
void map_assignment(enable_if_t<!is_copy_assignable<typename Map::mapped_type>::value
&& is_copy_constructible<typename Map::mapped_type>::value,
Class_> &cl) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
cl.def("__setitem__", [](Map &m, const KeyType &k, const MappedType &v) {
// We can't use m[k] = v; because value type might not be default constructable
auto r = m.emplace(k, v);
if (!r.second) {
// value type is not copy assignable so the only way to insert it is to erase it
// first...
m.erase(r.first);
m.emplace(k, v);
}
});
}
template <typename Map, typename Class_>
auto map_if_insertion_operator(Class_ &cl, std::string const &name)
-> decltype(std::declval<std::ostream &>() << std::declval<typename Map::key_type>()
<< std::declval<typename Map::mapped_type>(),
void()) {
cl.def(
"__repr__",
[name](Map &m) {
std::ostringstream s;
s << name << '{';
bool f = false;
for (auto const &kv : m) {
if (f) {
s << ", ";
}
s << kv.first << ": " << kv.second;
f = true;
}
s << '}';
return s.str();
},
"Return the canonical string representation of this map.");
}
struct keys_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual bool contains(const handle &k) = 0;
virtual ~keys_view() = default;
};
struct values_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~values_view() = default;
};
struct items_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~items_view() = default;
};
template <typename Map>
struct KeysViewImpl : public detail::keys_view {
explicit KeysViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_key_iterator(map.begin(), map.end()); }
bool contains(const handle &k) override {
try {
return map.find(k.template cast<typename Map::key_type>()) != map.end();
} catch (const cast_error &) {
return false;
}
}
Map &map;
};
template <typename Map>
struct ValuesViewImpl : public detail::values_view {
explicit ValuesViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_value_iterator(map.begin(), map.end()); }
Map &map;
};
template <typename Map>
struct ItemsViewImpl : public detail::items_view {
explicit ItemsViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_iterator(map.begin(), map.end()); }
Map &map;
};
inline str format_message_key_error_key_object(handle py_key) {
str message = "pybind11::bind_map key";
if (!py_key) {
return message;
}
try {
message = str(py_key);
} catch (const std::exception &) {
try {
message = repr(py_key);
} catch (const std::exception &) {
return message;
}
}
const ssize_t cut_length = 100;
if (len(message) > 2 * cut_length + 3) {
return str(message[slice(0, cut_length, 1)]) + str("✄✄✄")
+ str(message[slice(-cut_length, static_cast<ssize_t>(len(message)), 1)]);
}
return message;
}
template <typename KeyType>
str format_message_key_error(const KeyType &key) {
object py_key;
try {
py_key = cast(key);
} catch (const std::exception &) {
do { // Trick to avoid "empty catch" warning/error.
} while (false);
}
return format_message_key_error_key_object(py_key);
}
PYBIND11_NAMESPACE_END(detail)
template <typename Map, typename holder_type = default_holder_type<Map>, typename... Args>
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
using KeysView = detail::keys_view;
using ValuesView = detail::values_view;
using ItemsView = detail::items_view;
using Class_ = class_<Map, holder_type>;
// If either type is a non-module-local bound type then make the map binding non-local as well;
// otherwise (e.g. both types are either module-local or converting) the map will be
// module-local.
auto *tinfo = detail::get_type_info(typeid(MappedType));
bool local = !tinfo || tinfo->module_local;
if (local) {
tinfo = detail::get_type_info(typeid(KeyType));
local = !tinfo || tinfo->module_local;
}
Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
// Wrap KeysView if it wasn't already wrapped
if (!detail::get_type_info(typeid(KeysView))) {
class_<KeysView> keys_view(scope, "KeysView", pybind11::module_local(local));
keys_view.def("__len__", &KeysView::len);
keys_view.def("__iter__",
&KeysView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
keys_view.def("__contains__", &KeysView::contains);
}
// Similarly for ValuesView:
if (!detail::get_type_info(typeid(ValuesView))) {
class_<ValuesView> values_view(scope, "ValuesView", pybind11::module_local(local));
values_view.def("__len__", &ValuesView::len);
values_view.def("__iter__",
&ValuesView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}
// Similarly for ItemsView:
if (!detail::get_type_info(typeid(ItemsView))) {
class_<ItemsView> items_view(scope, "ItemsView", pybind11::module_local(local));
items_view.def("__len__", &ItemsView::len);
items_view.def("__iter__",
&ItemsView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}
cl.def(init<>());
// Register stream insertion operator (if possible)
detail::map_if_insertion_operator<Map, Class_>(cl, name);
cl.def(
"__bool__",
[](const Map &m) -> bool { return !m.empty(); },
"Check whether the map is nonempty");
cl.def(
"__iter__",
[](Map &m) { return make_key_iterator(m.begin(), m.end()); },
keep_alive<0, 1>() /* Essential: keep map alive while iterator exists */
);
cl.def(
"keys",
[](Map &m) { return std::unique_ptr<KeysView>(new detail::KeysViewImpl<Map>(m)); },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);
cl.def(
"values",
[](Map &m) { return std::unique_ptr<ValuesView>(new detail::ValuesViewImpl<Map>(m)); },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);
cl.def(
"items",
[](Map &m) { return std::unique_ptr<ItemsView>(new detail::ItemsViewImpl<Map>(m)); },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);
cl.def(
"__getitem__",
[](Map &m, const KeyType &k) -> MappedType & {
auto it = m.find(k);
if (it == m.end()) {
set_error(PyExc_KeyError, detail::format_message_key_error(k));
throw error_already_set();
}
return it->second;
},
return_value_policy::reference_internal // ref + keepalive
);
cl.def("__contains__", [](Map &m, const KeyType &k) -> bool {
auto it = m.find(k);
if (it == m.end()) {
return false;
}
return true;
});
// Fallback for when the object is not of the key type
cl.def("__contains__", [](Map &, const object &) -> bool { return false; });
// Assignment provided only if the type is copyable
detail::map_assignment<Map, Class_>(cl);
cl.def("__delitem__", [](Map &m, const KeyType &k) {
auto it = m.find(k);
if (it == m.end()) {
set_error(PyExc_KeyError, detail::format_message_key_error(k));
throw error_already_set();
}
m.erase(it);
});
// Always use a lambda in case of `using` declaration
cl.def("__len__", [](const Map &m) { return m.size(); });
return cl;
}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,299 @@
/*
pybind11/subinterpreter.h: Support for creating and using subinterpreters
Copyright (c) 2025 The Pybind Development Team.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "detail/common.h"
#include "detail/internals.h"
#include "gil.h"
#include <stdexcept>
#ifndef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
# error "This platform does not support subinterpreters, do not include this file."
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
inline PyInterpreterState *get_interpreter_state_unchecked() {
auto cur_tstate = get_thread_state_unchecked();
if (cur_tstate)
return cur_tstate->interp;
else
return nullptr;
}
PYBIND11_NAMESPACE_END(detail)
class subinterpreter;
/// Activate the subinterpreter and acquire its GIL, while also releasing any GIL and interpreter
/// currently held. Upon exiting the scope, the previous subinterpreter (if any) and its
/// associated GIL are restored to their state as they were before the scope was entered.
class subinterpreter_scoped_activate {
public:
explicit subinterpreter_scoped_activate(subinterpreter const &si);
~subinterpreter_scoped_activate();
subinterpreter_scoped_activate(subinterpreter_scoped_activate &&) = delete;
subinterpreter_scoped_activate(subinterpreter_scoped_activate const &) = delete;
subinterpreter_scoped_activate &operator=(subinterpreter_scoped_activate &) = delete;
subinterpreter_scoped_activate &operator=(subinterpreter_scoped_activate const &) = delete;
private:
PyThreadState *old_tstate_ = nullptr;
PyThreadState *tstate_ = nullptr;
PyGILState_STATE gil_state_;
bool simple_gil_ = false;
};
/// Holds a Python subinterpreter instance
class subinterpreter {
public:
/// empty/unusable, but move-assignable. use create() to create a subinterpreter.
subinterpreter() = default;
subinterpreter(subinterpreter const &copy) = delete;
subinterpreter &operator=(subinterpreter const &copy) = delete;
subinterpreter(subinterpreter &&old) noexcept
: istate_(old.istate_), creation_tstate_(old.creation_tstate_) {
old.istate_ = nullptr;
old.creation_tstate_ = nullptr;
}
subinterpreter &operator=(subinterpreter &&old) noexcept {
std::swap(old.istate_, istate_);
std::swap(old.creation_tstate_, creation_tstate_);
return *this;
}
/// Create a new subinterpreter with the specified configuration
/// @note This function acquires (and then releases) the main interpreter GIL, but the main
/// interpreter and its GIL are not required to be held prior to calling this function.
static inline subinterpreter create(PyInterpreterConfig const &cfg) {
error_scope err_scope;
subinterpreter result;
{
// we must hold the main GIL in order to create a subinterpreter
subinterpreter_scoped_activate main_guard(main());
auto prev_tstate = PyThreadState_Get();
PyStatus status;
{
/*
Several internal CPython modules are lacking proper subinterpreter support in 3.12
even though it is "stable" in that version. This most commonly seems to cause
crashes when two interpreters concurrently initialize, which imports several things
(like builtins, unicode, codecs).
*/
#if PY_VERSION_HEX < 0x030D0000 && defined(Py_MOD_PER_INTERPRETER_GIL_SUPPORTED)
static std::mutex one_at_a_time;
std::lock_guard<std::mutex> guard(one_at_a_time);
#endif
status = Py_NewInterpreterFromConfig(&result.creation_tstate_, &cfg);
}
// this doesn't raise a normal Python exception, it provides an exit() status code.
if (PyStatus_Exception(status)) {
pybind11_fail("failed to create new sub-interpreter");
}
// upon success, the new interpreter is activated in this thread
result.istate_ = result.creation_tstate_->interp;
detail::get_num_interpreters_seen() += 1; // there are now many interpreters
detail::get_internals(); // initialize internals.tstate, amongst other things...
// In 3.13+ this state should be deleted right away, and the memory will be reused for
// the next threadstate on this interpreter. However, on 3.12 we cannot do that, we
// must keep it around (but not use it) ... see destructor.
#if PY_VERSION_HEX >= 0x030D0000
PyThreadState_Clear(result.creation_tstate_);
PyThreadState_DeleteCurrent();
#endif
// we have to switch back to main, and then the scopes will handle cleanup
PyThreadState_Swap(prev_tstate);
}
return result;
}
/// Calls create() with a default configuration of an isolated interpreter that disallows fork,
/// exec, and Python threads.
static inline subinterpreter create() {
// same as the default config in the python docs
PyInterpreterConfig cfg;
std::memset(&cfg, 0, sizeof(cfg));
cfg.allow_threads = 1;
cfg.check_multi_interp_extensions = 1;
cfg.gil = PyInterpreterConfig_OWN_GIL;
return create(cfg);
}
~subinterpreter() {
if (!creation_tstate_) {
// non-owning wrapper, do nothing.
return;
}
PyThreadState *destroy_tstate;
PyThreadState *old_tstate;
// Python 3.12 requires us to keep the original PyThreadState alive until we are ready to
// destroy the interpreter. We prefer to use that to destroy the interpreter.
#if PY_VERSION_HEX < 0x030D0000
// The tstate passed to Py_EndInterpreter MUST have been created on the current OS thread.
bool same_thread = false;
# ifdef PY_HAVE_THREAD_NATIVE_ID
same_thread = PyThread_get_thread_native_id() == creation_tstate_->native_thread_id;
# endif
if (same_thread) {
// OK it is safe to use the creation state here
destroy_tstate = creation_tstate_;
old_tstate = PyThreadState_Swap(destroy_tstate);
} else {
// We have to make a new tstate on this thread and use that.
destroy_tstate = PyThreadState_New(istate_);
old_tstate = PyThreadState_Swap(destroy_tstate);
// We can use the one we just created, so we must delete the creation state.
PyThreadState_Clear(creation_tstate_);
PyThreadState_Delete(creation_tstate_);
}
#else
destroy_tstate = PyThreadState_New(istate_);
old_tstate = PyThreadState_Swap(destroy_tstate);
#endif
bool switch_back = old_tstate && old_tstate->interp != istate_;
// Internals always exists in the subinterpreter, this class enforces it when it creates
// the subinterpreter. Even if it didn't, this only creates the pointer-to-pointer, not the
// internals themselves.
detail::get_internals_pp_manager().get_pp();
detail::get_local_internals_pp_manager().get_pp();
// End it
Py_EndInterpreter(destroy_tstate);
// It's possible for the internals to be created during endinterpreter (e.g. if a
// py::capsule calls `get_internals()` during destruction), so we destroy afterward.
detail::get_internals_pp_manager().destroy();
detail::get_local_internals_pp_manager().destroy();
// switch back to the old tstate and old GIL (if there was one)
if (switch_back)
PyThreadState_Swap(old_tstate);
}
/// Get a handle to the main interpreter that can be used with subinterpreter_scoped_activate
/// Note that destructing the handle is a noop, the main interpreter can only be ended by
/// py::finalize_interpreter()
static subinterpreter main() {
subinterpreter m;
m.istate_ = PyInterpreterState_Main();
m.disarm(); // make destruct a noop
return m;
}
/// Get a non-owning wrapper of the currently active interpreter (if any)
static subinterpreter current() {
subinterpreter c;
c.istate_ = detail::get_interpreter_state_unchecked();
c.disarm(); // make destruct a noop, we don't own this...
return c;
}
/// Get the numerical identifier for the sub-interpreter
int64_t id() const {
if (istate_ != nullptr)
return PyInterpreterState_GetID(istate_);
else
return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
// here.
}
/// Get the interpreter's state dict. This interpreter's GIL must be held before calling!
dict state_dict() { return reinterpret_borrow<dict>(PyInterpreterState_GetDict(istate_)); }
/// abandon cleanup of this subinterpreter (leak it). this might be needed during
/// finalization...
void disarm() { creation_tstate_ = nullptr; }
/// An empty wrapper cannot be activated
bool empty() const { return istate_ == nullptr; }
/// Is this wrapper non-empty
explicit operator bool() const { return !empty(); }
private:
friend class subinterpreter_scoped_activate;
PyInterpreterState *istate_ = nullptr;
PyThreadState *creation_tstate_ = nullptr;
};
class scoped_subinterpreter {
public:
scoped_subinterpreter() : si_(subinterpreter::create()), scope_(si_) {}
explicit scoped_subinterpreter(PyInterpreterConfig const &cfg)
: si_(subinterpreter::create(cfg)), scope_(si_) {}
private:
subinterpreter si_;
subinterpreter_scoped_activate scope_;
};
inline subinterpreter_scoped_activate::subinterpreter_scoped_activate(subinterpreter const &si) {
if (!si.istate_) {
pybind11_fail("null subinterpreter");
}
if (detail::get_interpreter_state_unchecked() == si.istate_) {
// we are already on this interpreter, make sure we hold the GIL
simple_gil_ = true;
gil_state_ = PyGILState_Ensure();
return;
}
// we can't really interact with the interpreter at all until we switch to it
// not even to, for example, look in its state dict or touch its internals
tstate_ = PyThreadState_New(si.istate_);
// make the interpreter active and acquire the GIL
old_tstate_ = PyThreadState_Swap(tstate_);
// save this in internals for scoped_gil calls
detail::get_internals().tstate = tstate_;
}
inline subinterpreter_scoped_activate::~subinterpreter_scoped_activate() {
if (simple_gil_) {
// We were on this interpreter already, so just make sure the GIL goes back as it was
PyGILState_Release(gil_state_);
} else {
if (tstate_) {
#if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (detail::get_thread_state_unchecked() != tstate_) {
pybind11_fail("~subinterpreter_scoped_activate: thread state must be current!");
}
#endif
detail::get_internals().tstate.reset();
PyThreadState_Clear(tstate_);
PyThreadState_DeleteCurrent();
}
// Go back the previous interpreter (if any) and acquire THAT gil
PyThreadState_Swap(old_tstate_);
}
}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2021 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include "detail/common.h"
#include "detail/using_smart_holder.h"
#include "detail/value_and_holder.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code.
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
PYBIND11_NAMESPACE_END(detail)
// The original core idea for this struct goes back to PyCLIF:
// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37
// URL provided here mainly to give proper credit.
struct trampoline_self_life_support {
// NOTE: PYBIND11_INTERNALS_VERSION needs to be bumped if changes are made to this struct.
detail::value_and_holder v_h;
trampoline_self_life_support() = default;
void activate_life_support(const detail::value_and_holder &v_h_) {
Py_INCREF((PyObject *) v_h_.inst);
v_h = v_h_;
}
void deactivate_life_support() {
Py_DECREF((PyObject *) v_h.inst);
v_h = detail::value_and_holder();
}
~trampoline_self_life_support() {
if (v_h.inst != nullptr && v_h.vh != nullptr) {
void *value_void_ptr = v_h.value_ptr();
if (value_void_ptr != nullptr) {
PyGILState_STATE threadstate = PyGILState_Ensure();
v_h.value_ptr() = nullptr;
v_h.holder<smart_holder>().release_disowned();
detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type);
Py_DECREF((PyObject *) v_h.inst); // Must be after deregister.
PyGILState_Release(threadstate);
}
}
}
// For the next two, the default implementations generate undefined behavior (ASAN failures
// manually verified). The reason is that v_h needs to be kept default-initialized.
trampoline_self_life_support(const trampoline_self_life_support &) {}
trampoline_self_life_support(trampoline_self_life_support &&) noexcept {}
// These should never be needed (please provide test cases if you think they are).
trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete;
trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete;
};
PYBIND11_NAMESPACE_BEGIN(detail)
using get_trampoline_self_life_support_fn = trampoline_self_life_support *(*) (void *);
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
#include "detail/common.h"
#include "detail/descr.h"
#include "cast.h"
#include "pytypes.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<PyObject> {
public:
static constexpr auto name = const_name("object"); // See discussion under PR #4601.
// This overload is purely to guard against accidents.
template <typename T,
detail::enable_if_t<!is_same_ignoring_cvref<T, PyObject *>::value, int> = 0>
static handle cast(T &&, return_value_policy, handle /*parent*/) {
static_assert(is_same_ignoring_cvref<T, PyObject *>::value,
"Invalid C++ type T for to-Python conversion (type_caster<PyObject>).");
return nullptr; // Unreachable.
}
static handle cast(PyObject *src, return_value_policy policy, handle /*parent*/) {
if (src == nullptr) {
throw error_already_set();
}
if (PyErr_Occurred()) {
raise_from(PyExc_SystemError, "src != nullptr but PyErr_Occurred()");
throw error_already_set();
}
if (policy == return_value_policy::take_ownership) {
return src;
}
if (policy == return_value_policy::reference
|| policy == return_value_policy::automatic_reference) {
return handle(src).inc_ref();
}
pybind11_fail("type_caster<PyObject>::cast(): unsupported return_value_policy: "
+ std::to_string(static_cast<int>(policy)));
}
bool load(handle src, bool) {
value = reinterpret_borrow<object>(src);
return true;
}
template <typename T>
using cast_op_type = PyObject *;
explicit operator PyObject *() { return value.ptr(); }
private:
object value;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,298 @@
/*
pybind11/typing.h: Convenience wrapper classes for basic Python types
with more explicit annotations.
Copyright (c) 2023 Dustin Spicuzza <dustin@virtualroadside.com>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "detail/common.h"
#include "cast.h"
#include "pytypes.h"
#include <algorithm>
#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L
# define PYBIND11_TYPING_H_HAS_STRING_LITERAL
# include <numeric>
# include <ranges>
# include <string_view>
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(typing)
/*
The following types can be used to direct pybind11-generated docstrings
to have have more explicit types (e.g., `list[str]` instead of `list`).
Just use these in place of existing types.
There is no additional enforcement of types at runtime.
*/
template <typename... Types>
class Tuple : public tuple {
using tuple::tuple;
};
template <typename K, typename V>
class Dict : public dict {
using dict::dict;
};
template <typename T>
class List : public list {
using list::list;
};
template <typename T>
class Set : public set {
using set::set;
};
template <typename T>
class Iterable : public iterable {
using iterable::iterable;
};
template <typename T>
class Iterator : public iterator {
using iterator::iterator;
};
template <typename Signature>
class Callable;
template <typename Return, typename... Args>
class Callable<Return(Args...)> : public function {
using function::function;
};
template <typename T>
class Type : public type {
using type::type;
};
template <typename... Types>
class Union : public object {
PYBIND11_OBJECT_DEFAULT(Union, object, PyObject_Type)
using object::object;
};
template <typename T>
class Optional : public object {
PYBIND11_OBJECT_DEFAULT(Optional, object, PyObject_Type)
using object::object;
};
template <typename T>
class Final : public object {
PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type)
using object::object;
};
template <typename T>
class ClassVar : public object {
PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type)
using object::object;
};
template <typename T>
class TypeGuard : public bool_ {
using bool_::bool_;
};
template <typename T>
class TypeIs : public bool_ {
using bool_::bool_;
};
class NoReturn : public none {
using none::none;
};
class Never : public none {
using none::none;
};
#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL)
template <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); }
char name[N];
};
template <StringLiteral... StrLits>
class Literal : public object {
PYBIND11_OBJECT_DEFAULT(Literal, object, PyObject_Type)
};
// Example syntax for creating a TypeVar.
// typedef typing::TypeVar<"T"> TypeVarT;
template <StringLiteral>
class TypeVar : public object {
PYBIND11_OBJECT_DEFAULT(TypeVar, object, PyObject_Type)
using object::object;
};
#endif
PYBIND11_NAMESPACE_END(typing)
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename... Types>
struct handle_type_name<typing::Tuple<Types...>> {
static constexpr auto name = const_name("tuple[")
+ ::pybind11::detail::concat(make_caster<Types>::name...)
+ const_name("]");
};
template <>
struct handle_type_name<typing::Tuple<>> {
// PEP 484 specifies this syntax for an empty tuple
static constexpr auto name = const_name("tuple[()]");
};
template <typename T>
struct handle_type_name<typing::Tuple<T, ellipsis>> {
// PEP 484 specifies this syntax for a variable-length tuple
static constexpr auto name
= const_name("tuple[") + make_caster<T>::name + const_name(", ...]");
};
template <typename K, typename V>
struct handle_type_name<typing::Dict<K, V>> {
static constexpr auto name = const_name("dict[") + make_caster<K>::name + const_name(", ")
+ make_caster<V>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::List<T>> {
static constexpr auto name = const_name("list[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Set<T>> {
static constexpr auto name = const_name("set[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Iterable<T>> {
static constexpr auto name
= const_name("collections.abc.Iterable[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Iterator<T>> {
static constexpr auto name
= const_name("collections.abc.Iterator[") + make_caster<T>::name + const_name("]");
};
template <typename Return, typename... Args>
struct handle_type_name<typing::Callable<Return(Args...)>> {
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
static constexpr auto name
= const_name("collections.abc.Callable[[")
+ ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...)
+ const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name)
+ const_name("]");
};
template <typename Return>
struct handle_type_name<typing::Callable<Return(ellipsis)>> {
// PEP 484 specifies this syntax for defining only return types of callables
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
static constexpr auto name = const_name("collections.abc.Callable[..., ")
+ ::pybind11::detail::return_descr(make_caster<retval_type>::name)
+ const_name("]");
};
template <typename T>
struct handle_type_name<typing::Type<T>> {
static constexpr auto name = const_name("type[") + make_caster<T>::name + const_name("]");
};
template <typename... Types>
struct handle_type_name<typing::Union<Types...>> {
static constexpr auto name = ::pybind11::detail::union_concat(make_caster<Types>::name...);
};
template <typename T>
struct handle_type_name<typing::Optional<T>> {
static constexpr auto name = make_caster<T>::name | make_caster<none>::name;
};
template <typename T>
struct handle_type_name<typing::Final<T>> {
static constexpr auto name = const_name("typing.Final[")
+ ::pybind11::detail::return_descr(make_caster<T>::name)
+ const_name("]");
};
template <typename T>
struct handle_type_name<typing::ClassVar<T>> {
static constexpr auto name
= const_name("typing.ClassVar[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::TypeGuard<T>> {
static constexpr auto name = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[")
+ make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::TypeIs<T>> {
static constexpr auto name = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[")
+ make_caster<T>::name + const_name("]");
};
template <>
struct handle_type_name<typing::NoReturn> {
static constexpr auto name = const_name("typing.NoReturn");
};
template <>
struct handle_type_name<typing::Never> {
static constexpr auto name = const_name(PYBIND11_NEVER_TYPE_HINT);
};
#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL)
template <typing::StringLiteral StrLit>
consteval auto sanitize_string_literal() {
constexpr std::string_view v(StrLit.name);
constexpr std::string_view special_chars("!@%{}-");
constexpr auto num_special_chars = std::accumulate(
special_chars.begin(), special_chars.end(), (size_t) 0, [&v](auto acc, const char &c) {
return std::move(acc) + std::ranges::count(v, c);
});
char result[v.size() + num_special_chars + 1];
size_t i = 0;
for (auto c : StrLit.name) {
if (special_chars.find(c) != std::string_view::npos) {
result[i++] = '!';
}
result[i++] = c;
}
return typing::StringLiteral(result);
}
template <typing::StringLiteral... Literals>
struct handle_type_name<typing::Literal<Literals...>> {
static constexpr auto name
= const_name("typing.Literal[")
+ pybind11::detail::concat(const_name(sanitize_string_literal<Literals>().name)...)
+ const_name("]");
};
template <typing::StringLiteral StrLit>
struct handle_type_name<typing::TypeVar<StrLit>> {
static constexpr auto name = const_name(sanitize_string_literal<StrLit>().name);
};
#endif
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -0,0 +1,75 @@
/*
pybind11/warnings.h: Python warnings wrappers.
Copyright (c) 2024 Jan Iwaszkiewicz <jiwaszkiewicz6@gmail.com>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include "detail/common.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
inline bool PyWarning_Check(PyObject *obj) {
int result = PyObject_IsSubclass(obj, PyExc_Warning);
if (result == 1) {
return true;
}
if (result == -1) {
raise_from(PyExc_SystemError,
"pybind11::detail::PyWarning_Check(): PyObject_IsSubclass() call failed.");
throw error_already_set();
}
return false;
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_BEGIN(warnings)
inline object
new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarning) {
if (!detail::PyWarning_Check(base.ptr())) {
pybind11_fail("pybind11::warnings::new_warning_type(): cannot create custom warning, base "
"must be a subclass of "
"PyExc_Warning!");
}
if (hasattr(scope, name)) {
pybind11_fail("pybind11::warnings::new_warning_type(): an attribute with name \""
+ std::string(name) + "\" exists already.");
}
std::string full_name = scope.attr("__name__").cast<std::string>() + std::string(".") + name;
handle h(PyErr_NewException(full_name.c_str(), base.ptr(), nullptr));
if (!h) {
raise_from(PyExc_SystemError,
"pybind11::warnings::new_warning_type(): PyErr_NewException() call failed.");
throw error_already_set();
}
auto obj = reinterpret_steal<object>(h);
scope.attr(name) = obj;
return obj;
}
// Similar to Python `warnings.warn()`
inline void
warn(const char *message, handle category = PyExc_RuntimeWarning, int stack_level = 2) {
if (!detail::PyWarning_Check(category.ptr())) {
pybind11_fail(
"pybind11::warnings::warn(): cannot raise warning, category must be a subclass of "
"PyExc_Warning!");
}
if (PyErr_WarnEx(category.ptr(), message, stack_level) == -1) {
throw error_already_set();
}
}
PYBIND11_NAMESPACE_END(warnings)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

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