Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9c9c5a0
Add basic functionality for geometry swapping extension
nefelimet Aug 26, 2025
c195113
doc strings added
svandenb-dev Aug 27, 2025
09df176
Add function that moves DXF polygon by center point
nefelimet Sep 3, 2025
4a0aee1
FIX: grpc fix
gkorompi Sep 11, 2025
16f18dd
Add basic functionality for geometry swapping extension
nefelimet Aug 26, 2025
be8351a
doc strings added
svandenb-dev Aug 27, 2025
0ae61af
Add function that moves DXF polygon by center point
nefelimet Sep 3, 2025
a3875d9
FIX: grpc fix
gkorompi Sep 11, 2025
7953328
Merge remote-tracking branch 'origin/geom_swap_ext' into geom_swap_ext
nefelimet Sep 16, 2025
fe677e4
MISC: Auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 16, 2025
0ce7ab8
Add testing
nefelimet Sep 23, 2025
fc699f4
MISC: Auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2025
c0cdd8b
FIX: extension in grp test
gkorompi Sep 23, 2025
b2b3937
Fix testing
nefelimet Oct 15, 2025
083cfb6
MISC: Auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 15, 2025
6d7d1a8
Add test without center point defined
nefelimet Oct 15, 2025
762b2a1
Merge remote-tracking branch 'origin/geom_swap_ext' into geom_swap_ext
nefelimet Oct 15, 2025
48c7f68
MISC: Auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 15, 2025
1b25881
FIX: dxf fix
gkorompi Oct 23, 2025
35c7462
Merge branch 'main' into geom_swap_ext
gkorompi Oct 23, 2025
d2b5b4a
MISC: Auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 23, 2025
a8fb6f7
chore: adding changelog file 1529.added.md [dependabot-skip]
pyansys-ci-bot Oct 23, 2025
218a993
FIX: dxf fix
gkorompi Oct 23, 2025
bfb7d33
Add ezdxf in dependencies list
nefelimet Oct 23, 2025
302ced1
FIX: dxf fix
gkorompi Oct 23, 2025
607c446
FEATURE: dxf path folder
gkorompi Oct 24, 2025
e33d656
Merge branch 'main' into geom_swap_ext
svandenb-dev Oct 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/1529.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add functionality for geometry swapping from DXF file
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ dependencies = [
"aiohttp>=3.8",
"python-socketio>=5.10",
"requests>=2.32,<3.0",
"ezdxf>=1.4.2",
]

[project.optional-dependencies]
Expand Down
250 changes: 250 additions & 0 deletions src/pyedb/extensions/dxf_swap_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List

from ezdxf import readfile

from pyedb import Edb
from pyedb.grpc.database.primitive.primitive import Primitive

# TODO: Fix units issue


def create_polygon_from_dxf(edb: Edb, dxf_path: str, layer_name: str) -> Primitive:
"""
Create a polygon primitive in the EDB layout from the first entity
found in a DXF file.

Parameters
----------
edb : Edb
An active EDB (Electronics Database) instance that the polygon
will be added to.
dxf_path : str
Absolute or relative path to the DXF file containing the
geometry to import.
layer_name : str
Name of the EDB layer on which the polygon will be created.

Returns
-------
Primitive
The newly created polygon primitive object.

Notes
-----
* Only the first entity in the DXF modelspace is used; all other
entities are ignored.
* Coordinates in the DXF are assumed to be in **microns** and are
divided by 1000 to convert to **millimetres** before being passed
to EDB.
* The resulting polygon is **not** translated or rotated; it is
placed exactly as defined in the DXF (after scaling).

Examples
--------
>>> edb = Edb(edbpath="my_design.aedb")
>>> poly = create_polygon_from_dxf(edb, dxf_path="outline.dxf", layer_name="Outline")
>>> print(poly)
<pyedb.dotnet.edb_core.edb_data.primitives.EDBPrimitives object ...>
"""
doc = readfile(dxf_path)
msp = doc.modelspace()
shape = msp[0]
points = list(shape.get_points())
points_2D = [point[:2] for point in points]
points_2D = [[x / 1000, y / 1000] for x, y in points_2D]
return edb.modeler.create_polygon(points_2D, layer_name=layer_name)


def swap_polygon_with_dxf(edb: Edb, dxf_path: str, layer_name: str, point_dxf: List[str], point_aedt: List[str]):
"""
Replace an existing polygon on a given layer with a new polygon
imported from a DXF file, aligning the two geometries via reference
points.

Parameters
----------
edb : Edb
An active EDB instance.
dxf_path : str
Path to the DXF file that contains the replacement geometry.
layer_name : str
Layer on which the old polygon will be deleted and the new one
created.
point_dxf : List[str]
Two-element list of strings specifying the **reference point**
inside the DXF geometry (units: **mm**).
Example: ``["-1.5", "4.2"]``.
point_aedt : List[str]
Two-element list of strings specifying the **target location**
in the EDB layout where the DXF reference point should land
(units: **mm**).
Example: ["12.0", "7.5"].

Returns
-------
None
The function operates in-place on the provided EDB instance.

Workflow
--------
1. Identify and delete the existing polygon that encloses
``point_aedt`` on the specified layer.
2. Import the first entity from the DXF file as a new polygon on
the same layer (see :func:`create_polygon_from_dxf`).
3. Translate the new polygon so that ``point_dxf`` coincides with
``point_aedt``.

Notes
-----
* Only the first entity in the DXF modelspace is used.
* Both input points are internally converted to **metres** before
computing the translation vector.
* The translation vector is then scaled back to **millimetres**
and expressed as strings with an "mm" suffix, as required by
the EDB move API.

Examples
--------
>>> edb = Edb(edbpath="design.aedb")
>>> swap_polygon_with_dxf(
... edb,
... dxf_path="new_outline.dxf",
... layer_name="Outline",
... point_dxf=["0", "0"], # Origin in DXF
... point_aedt=["10", "5"], # Where DXF origin should land
... )
>>> edb.save()
"""
if not float(edb.version) >= 2025.2:
raise AttributeError("This function is only supported with ANSYS release 2025R2 and higher.")
prim_to_delete = edb.modeler.get_primitive_by_layer_and_point(point=point_aedt, layer=layer_name)
prim_to_delete = prim_to_delete[0]
prim_to_delete.delete()

dxf_polygon = create_polygon_from_dxf(edb, dxf_path, layer_name)

point_dxf_double = [
edb.value(point_dxf[0]),
edb.value(point_dxf[1]),
]
point_aedt_double = [
edb.value(point_aedt[0]),
edb.value(point_aedt[1]),
]
move_vector_double = [
point_aedt_double[0] - point_dxf_double[0],
point_aedt_double[1] - point_dxf_double[1],
]
move_vector = [
f"{1000 * move_vector_double[0]}mm",
f"{1000 * move_vector_double[1]}mm",
]

dxf_polygon.move(vector=move_vector)


def swap_polygon_with_dxf_center_point(edb: Edb, dxf_path: str, layer_name: str, point_aedt: List[str]):
"""
Replace an existing polygon on a given layer with a new polygon
imported from a DXF file, aligning the two geometries via reference
points. Uses the center point of the DXF polygon as reference (not an input point).

Parameters
----------
edb : Edb
An active EDB instance.
dxf_path : str
Path to the DXF file that contains the replacement geometry.
layer_name : str
Layer on which the old polygon will be deleted and the new one
created.
point_aedt : List[str]
Two-element list of strings specifying the **target location**
in the EDB layout where the DXF reference point should land
(units: **mm**).
Example: ["12.0", "7.5"].

Returns
-------
None
The function operates in-place on the provided EDB instance.

Workflow
--------
1. Identify and delete the existing polygon that encloses
``point_aedt`` on the specified layer.
2. Import the first entity from the DXF file as a new polygon on
the same layer (see :func:`create_polygon_from_dxf`).
3. Translate the new polygon so that ``point_dxf`` coincides with
``point_aedt``.

Notes
-----
* Only the first entity in the DXF modelspace is used.
* Both input points are internally converted to **metres** before
computing the translation vector.
* The translation vector is then scaled back to **millimetres**
and expressed as strings with an "mm" suffix, as required by
the EDB move API.

Examples
--------
>>> edb = Edb(edbpath="design.aedb")
>>> swap_polygon_with_dxf(
... edb,
... dxf_path="new_outline.dxf",
... layer_name="Outline",
... point_aedt=["10", "5"], # Where DXF origin should land
... )
>>> edb.save()
"""
if not float(edb.version) >= 2025.2:
raise AttributeError("This function is only supported with ANSYS release 2025R2 and higher.")
prim_to_delete = edb.modeler.get_primitive_by_layer_and_point(point=point_aedt, layer=layer_name)
prim_to_delete = prim_to_delete[0]
prim_to_delete.delete()

dxf_polygon = create_polygon_from_dxf(edb, dxf_path, layer_name)
point_dxf = dxf_polygon.center
point_dxf = [f"{x.value * 1000}mm" for x in point_dxf]

point_dxf_double = [
edb.value(point_dxf[0]),
edb.value(point_dxf[1]),
]
point_aedt_double = [
edb.value(point_aedt[0]),
edb.value(point_aedt[1]),
]
move_vector_double = [
point_aedt_double[0] - point_dxf_double[0],
point_aedt_double[1] - point_dxf_double[1],
]
move_vector = [
f"{1000 * move_vector_double[0].value}mm",
f"{1000 * move_vector_double[1].value}mm",
]

dxf_polygon.move(vector=move_vector)
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ def load_edb(self, edb_path, copy_to_temp=True, **kwargs):
aedb = edb_path
return Edb(edbpath=aedb, edbversion=desktop_version, grpc=self.grpc, **kwargs)

def load_dxf_edb(self):
aedb = self._copy_file_folder_into_local_folder("dxf_swap/starting_edb/starting_edb.aedb")
return Edb(edbpath=aedb, version=desktop_version, grpc=True)

def copy_project_for_job_manager(self, local_scratch):
example_project = os.path.join(example_models_path, "test_project_for_job_manager.aedb")
target_path = os.path.join(local_scratch.path, "project.aedb")
Expand Down
Loading
Loading