NativeAOT backend

The optional acadsharp-native package runs ACadSharp from CPython through ctypes. It contains a .NET 10 NativeAOT shared library, including its runtime, so neither pythonnet nor a separately installed .NET runtime is needed. The managed backend provides the complete CLR interop surface; this native backend provides object construction, editing, inspection and file I/O subject to the NativeAOT restrictions below.

Prebuilt wheels for 0.1.0 target Linux x64/Arm64, macOS Arm64 (Apple Silicon), and Windows x64. Intel macOS users can use the managed backend or build locally with --rid osx-x64. Hosted CI skips Intel macOS, so this release does not include an Intel macOS native wheel.

pip install acadsharp-native
from acadsharp import native

with native.load() as cad:
    document = cad.ACadSharp.CadDocument()
    line = cad.ACadSharp.Entities.Line()
    line.StartPoint = cad.CSMath.XYZ(1, 2, 3)
    line.EndPoint = cad.CSMath.XYZ(10, 20, 30)
    document.Entities.Add(line)

    layer = cad.ACadSharp.Tables.Layer("Geometry")
    document.Layers.Add(layer)
    line.Layer = layer

    cad.write(document, "drawing.dwg")
    cad.write(document, "drawing.dxf")
    loaded = cad.read("drawing.dwg")
    for entity in loaded.Entities:
        print(entity.type_name, entity.Handle, entity.Layer.Name)

read and write infer dxf or dwg from the file extension. Pass format= for another extension and binary=True for binary DXF. These operations use the same upstream ACadSharp readers and writers as the managed package. Upstream format/version support and limitations apply unchanged.

Object model

All 607 public types in the pinned ACadSharp assembly are discoverable through cad.types, including the CSMath and CSUtilities namespaces compiled into ACadSharp. Namespaces and nested types support attribute access, such as cad.ACadSharp.Entities.LwPolyline.Vertex(2, 3). Use cad.type(full_name) for names that cannot be written as Python identifiers, and cad.describe(full_name) for constructors, properties, fields, method signatures, events and nested types. instance.describe() also works for returned closed generic collection types.

Ordinary public constructors, methods, properties and fields are reflected at runtime. Static members are accessed on the type proxy. CLR enums are integer values carrying type_name and name. Python lists and tuples convert to arrays when a method accepts a compatible array or sequence interface. Collection proxies support len, iteration, integer/name indexing and writable indexers. Their upstream methods, such as Add, Remove, and Clear, remain available. Color.GetRgb(), Color.GetTrueColorRgb(), and Color.GetIndexRGB(index) return byte-array proxies, which can be converted with list(...) or bytes(...). These three upstream methods return unboxable ReadOnlySpan<byte> values; the native bridge uses compiled adapters while preserving their method names.

with native.load() as cad:
    doc = cad.ACadSharp.CadDocument(cad.ACadSharp.ACadVersion.AC1032)
    doc.Layers.Add(cad.ACadSharp.Tables.Layer("Objects"))
    success, layer = doc.Layers.TryGetValue("Objects", None)
    assert success and layer.Name == "Objects"
    point = cad.CSMath.XYZ.construct(5, signature=["System.Double"])
    print(point.X, point.Y, point.Z)

For an overloaded constructor, use construct(*args, signature=[...]) or pass signature= to the type call. Methods accept the same keyword through their attribute callable or object.call("MethodName", *args, signature=[...]). Signatures use fully qualified CLR parameter type names, including & for by-reference parameters. Ref/out arguments occupy their parameter positions; pass None for an out argument. The result is a tuple containing the return value followed by each ref/out value in declaration order. Ambiguous overloads raise an error asking for an explicit signature rather than selecting arbitrarily.

CLR value types are boxed copies. For example, editing line.StartPoint.X changes a temporary vector, so assign the whole value back:

point = line.StartPoint
point.X = 42
line.StartPoint = point

get(name), set(name, value) and call(name, ...) provide explicit member access if a CLR name conflicts with a Python proxy helper. Compare underlying objects using their CLR Equals method; separate Python proxies can refer to the same CLR object.

Ownership and errors

Use with native.load() as cad or call cad.close() to release every outstanding handle in that session. An individual proxy's close() releases its reference; other proxies to the same object remain valid. Garbage collection also releases proxy handles. Closed handles fail predictably when used again. Objects cannot be passed between NativeRuntime sessions.

Releasing a handle does not invoke the CLR object's Dispose method because other handles or the document can still refer to it. Invoke obj.Dispose() for an explicitly created disposable reader/writer/stream. Iteration disposes its internal enumerator automatically when exhausted or closed. The shared library stays loaded for the lifetime of the Python process.

The C ABI catches managed exceptions and returns their type and message. Python raises NativeError with a managed_type attribute. Missing attribute lookups become AttributeError; index and key failures become IndexError and KeyError. Calls are serialized within the native library, including calls from separate sessions. This provides bridge safety, not parallel execution of CAD operations.

NativeAOT restrictions

The native package is an optional backend, not a replacement for every feature of pythonnet. These features require the managed backend:

  • Constructing arbitrary closed generic CLR types or calling open generic methods with runtime type arguments. Existing compiled generic collections returned by ACadSharp remain usable.
  • Python callbacks, CLR delegate/event subscriptions, Python subclasses of CLR classes, and Python implementations of CLR interfaces.
  • Loading additional managed assemblies or JIT-compiling code at runtime.
  • Automatic conversions for arbitrary Python objects, dictionaries, file-like objects, NumPy arrays, or CLR types from unrelated assemblies.

The bridge preserves the complete ACadSharp assembly and reflection metadata using TrimmerRootAssembly and rd.xml. NativeAOT still emits trimming and dynamic-code analysis warnings for reflection and generic operations. These are not hidden: the test suite covers complete public-type discovery, every default constructible entity, metadata, model edits, collections, overloads, ref/out, ownership, all 256 indexed colors, and actual ASCII DXF, binary DXF and DWG round trips. Twenty-one upstream reference drawings also exercise R12–2018 file versions with complex drawings. This is evidence for those operations, not a guarantee that every conceivable reflected call is AOT compatible. Use the managed backend if an operation requires unavailable runtime code or reflection metadata.

Building from source

Install the .NET 10 SDK and the host platform's native toolchain. On macOS use Xcode command line tools; on Linux install clang and zlib development headers; on Windows install Visual Studio C++ build tools and the Windows SDK. Linux binaries retain .NET globalization support and require the system ICU libraries at runtime (for example, the distribution's libicu package).

git submodule update --init --recursive
python scripts/build_native.py

For a local Intel macOS build, run python scripts/build_native.py --rid osx-x64 on an Intel Mac. The release CI omission does not remove this build target.

The script publishes to artifacts/native/<RID>/, alongside build provenance and the upstream/.NET license notices. It accepts --rid and --output. NativeAOT builds should run on the target operating system; the supported release matrix is maintained in the CI workflows. Set the library explicitly for a source checkout:

export ACADSHARP_NATIVE_LIBRARY="$PWD/artifacts/native/osx-arm64/acadsharp_native.dylib"
python -m pytest tests/test_native.py

The other library names are acadsharp_native.so on Linux and acadsharp_native.dll on Windows. You can also pass the absolute library path to native.load(path). A built wheel finds its library through the acadsharp_native provider package. See releasing for packaging.

C ABI

acadsharp_native.h declares the three exports: acadsharp_abi_version, acadsharp_call and acadsharp_free. ABI version 1 accepts null-terminated UTF-8 JSON requests. Every allocated response, including an error, must be freed exactly once with acadsharp_free. The Python wrapper handles this in a finally block. Never use the C library's free on an allocation returned by this library.

Requests name an op: info, types, describe, create, get, set, invoke, getitem, setitem, length, iterate, next, dispose_iterator, read, write, string, or release. Object operations identify a numeric handle; static/constructor operations identify a fully qualified type. Successful responses have {"ok": true, "result": ...} and errors have {"ok": false, "error": "CLR.Exception.Type", "message": "..."}. Object results carry $ref and $type; references passed as arguments carry $ref. Enum values carry $enum, value and name. Nonfinite floating-point numbers carry $number. The wrapper source is the reference for the request schemas.