From d5ea1262b94688f947d33917ca90ffd6eb561f61 Mon Sep 17 00:00:00 2001 From: LasNikas Date: Mon, 2 Jun 2025 16:58:18 +0200 Subject: [PATCH 1/3] add DXF format --- src/preprocessing/geometries/io.jl | 94 +++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/src/preprocessing/geometries/io.jl b/src/preprocessing/geometries/io.jl index b08ca002a1..e8f2012c4b 100644 --- a/src/preprocessing/geometries/io.jl +++ b/src/preprocessing/geometries/io.jl @@ -2,7 +2,7 @@ load_geometry(filename; element_type=Float64) Load file and return corresponding type for [`ComplexShape`](@ref). -Supported file formats are `.stl` and `.asc`. +Supported file formats are `.stl`, `.asc` and `dxf`. # Arguments - `filename`: Name of the file to be loaded. @@ -17,10 +17,12 @@ function load_geometry(filename; element_type=Float64) if file_extension == ".asc" geometry = load_ascii(filename; ELTYPE, skipstart=1) + elseif file_extension == ".dxf" + geometry = load_dxf(filename; ELTYPE) elseif file_extension == ".stl" geometry = load(FileIO.query(filename); ELTYPE) else - throw(ArgumentError("Only `.stl` and `.asc` files are supported (yet).")) + throw(ArgumentError("Only `.stl`, `.asc` and `.dxf` files are supported (yet).")) end return geometry @@ -35,6 +37,94 @@ function load_ascii(filename; ELTYPE=Float64, skipstart=1) return Polygon(copy(points')) end +function load_dxf(filename; ELTYPE=Float64) + points = Tuple{ELTYPE, ELTYPE}[] + + load_dxf!(points, filename) + + return Polygon(stack(points)) +end + +function load_dxf!(points::Vector{Tuple{T, T}}, filename) where {T} + open(filename) do io + lines = readlines(io) + + # Check if the DXF file contains line entities ("LINE") or polyline entities ("POLYLINE") + idx_first_line = findfirst(x -> strip(x) == "LINE", lines) + idx_first_polyline = findfirst(x -> strip(x) == "POLYLINE", lines) + + if !isnothing(idx_first_line) && isnothing(idx_first_polyline) + # The file contains only simple line entities ("LINE") + i = idx_first_line + + while i <= length(lines) + if strip(lines[i]) == "LINE" + if idx_first_line == i + # For a polygon, we only need to store the start point of the first line entity. + # For subsequent lines, the end point of the previous edge is the start point of the current edge, + # so storing all start points would result in duplicate vertices. + # Therefore, we only push the start point for the very first line. + + # Search for the coordinates of the start point: + # Group codes "10", "20", "30" represent x, y, z of the start point + while i <= length(lines) && strip(lines[i]) != "10" + i += 1 + end + x1 = parse(T, strip(lines[i + 1])) + y1 = parse(T, strip(lines[i + 3])) # "20" is two lines further + + push!(points, (x1, y1)) + end + + # Search for the end point coordinates: + # Group codes "11", "21", "31" represent x, y, z of the end point + while i <= length(lines) && strip(lines[i]) != "11" + i += 1 + end + x2 = parse(T, strip(lines[i + 1])) + y2 = parse(T, strip(lines[i + 3])) # "21" is two lines further + + # Add end points of the line to the point list + push!(points, (x2, y2)) + end + i += 1 + end + + elseif isnothing(idx_first_line) && !isnothing(idx_first_polyline) + # The file contains only polyline entities ("POLYLINE") + i = idx_first_polyline + + while i <= length(lines) + line = strip(lines[i]) + if line == "VERTEX" + # Search for the coordinates of the current vertex: + # Group codes "10", "20", "30" represent x, y, z of the vertex + while i <= length(lines) && strip(lines[i]) != "10" + i += 1 + end + x = parse(T, strip(lines[i + 1])) + y = parse(T, strip(lines[i + 3])) # "20" is two lines further + + # Add the vertex to the point list + push!(points, (x, y)) + + elseif line == "SEQEND" + # End of the polyline reached + break + end + i += 1 + end + else + throw(ArgumentError("This entity type is not supported. Only 'LINE' OR 'POLYLINE' are allowed.")) + end + end + + # Remove duplicate points from the list + unique(points) + + return points +end + # FileIO.jl docs: # https://juliaio.github.io/FileIO.jl/stable/implementing/#All-at-once-I/O:-implementing-load-and-save function load(fn::FileIO.File{FileIO.format"STL_BINARY"}; element_types...) From 6a0e45fbf5592cd74053a00ddd1e488c2b001481 Mon Sep 17 00:00:00 2001 From: LasNikas Date: Mon, 2 Jun 2025 18:38:47 +0200 Subject: [PATCH 2/3] add docs for `load_geometry` --- docs/src/preprocessing/preprocessing.md | 43 +++++++++++++++++++++++++ src/preprocessing/geometries/io.jl | 2 ++ 2 files changed, 45 insertions(+) diff --git a/docs/src/preprocessing/preprocessing.md b/docs/src/preprocessing/preprocessing.md index 4be1f6c2f2..577c2134f5 100644 --- a/docs/src/preprocessing/preprocessing.md +++ b/docs/src/preprocessing/preprocessing.md @@ -250,6 +250,49 @@ Pages = [joinpath("preprocessing", "point_in_poly", "winding_number_hormann.jl") Modules = [TrixiParticles] Pages = [joinpath("preprocessing", "point_in_poly", "winding_number_jacobson.jl")] ``` +# [Read geometries from file](@id read_geometries_from_file) +Geometries can be imported using the [`load_geometry`](@ref) function. +- For 3D geometries, we support the binary (`.stl`) format. +- For 2D geometries, the recommended format is DXF (`.dxf`), with optional support for a simple ASCII (`.asc`) format. + +## ASCII Format (.asc) +An .asc file contains a list of 2D coordinates, space-delimited, one point per line, where the first column are the x values and the second the y values. +For example: +```plaintext +# ASCII +0.0 0.0 +1.0 0.0 +1.0 1.0 +0.0 1.0 +``` +It is the user’s responsibility to ensure the points are ordered correctly. +This format is easy to generate and inspect manually. + +## DXF Format (.dxf) – recommended +The DXF (Drawing Exchange Format) is a widely-used CAD format for 2D and 3D data. +We recommend this format for defining 2D polygonal. +Only DXF entities of type `LINE` or `POLYLINE` are supported. +To create DXF files from scratch, we recommend using the open-source tool [FreeCAD](https://www.freecad.org/). +For a less technical approach, we recommend using [Inkscape](https://inkscape.org/) to create SVG files and then export them as DXF. + +### Creating DXF Files with FreeCAD + +- Open FreeCAD and create a new document. +- Switch to the Sketcher workbench and create a new sketch. +- Choose the XY-plane orientation and draw your geometry. +- Select the object to be exported. +- Go to "File > Export..." and choose "AutoDesk DXF (*.dxf)" as the format. +- Ensure the following Import-Export options are enabled: + - "Use legacy Python exporter". + - "Project exported objects along current view direction". + +### Creating DXF Files with Inkscape +SVG vector graphics can also be used as a basis for geometry. +Use Inkscape to open or create the SVG. +You can simply draw a Bezier curve by pressing "B" on your keyboard. We reommend the mode "Create spiro paths" for a smooth curve. +Select the relevant path and export it as DXF: +- Go to "File > Save As...". +- Choose "Desktop Cutting Plotter (AutoCAD DXF R12)(*.dxf)" format. ```@autodocs Modules = [TrixiParticles] diff --git a/src/preprocessing/geometries/io.jl b/src/preprocessing/geometries/io.jl index e8f2012c4b..1365a3bf21 100644 --- a/src/preprocessing/geometries/io.jl +++ b/src/preprocessing/geometries/io.jl @@ -3,6 +3,8 @@ Load file and return corresponding type for [`ComplexShape`](@ref). Supported file formats are `.stl`, `.asc` and `dxf`. +For comprehensive information about the supported file formats, refer to the documentation at +[Read geometries from file](@ref read_geometries_from_file). # Arguments - `filename`: Name of the file to be loaded. From 7915ab22fee4da8e4a0be62cb3d6150b121dba9d Mon Sep 17 00:00:00 2001 From: LasNikas Date: Wed, 11 Jun 2025 18:47:38 +0200 Subject: [PATCH 3/3] implement suggestions --- docs/src/preprocessing/preprocessing.md | 6 ++++-- src/preprocessing/geometries/io.jl | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/src/preprocessing/preprocessing.md b/docs/src/preprocessing/preprocessing.md index 577c2134f5..aba434c82f 100644 --- a/docs/src/preprocessing/preprocessing.md +++ b/docs/src/preprocessing/preprocessing.md @@ -256,7 +256,8 @@ Geometries can be imported using the [`load_geometry`](@ref) function. - For 2D geometries, the recommended format is DXF (`.dxf`), with optional support for a simple ASCII (`.asc`) format. ## ASCII Format (.asc) -An .asc file contains a list of 2D coordinates, space-delimited, one point per line, where the first column are the x values and the second the y values. +An .asc file contains a list of 2D coordinates, space-delimited, one point per line, +where the first column are the x values and the second the y values. For example: ```plaintext # ASCII @@ -289,7 +290,8 @@ For a less technical approach, we recommend using [Inkscape](https://inkscape.or ### Creating DXF Files with Inkscape SVG vector graphics can also be used as a basis for geometry. Use Inkscape to open or create the SVG. -You can simply draw a Bezier curve by pressing "B" on your keyboard. We reommend the mode "Create spiro paths" for a smooth curve. +You can simply draw a Bezier curve by pressing "B" on your keyboard. +We reommend the mode "Create spiro paths" for a smooth curve. Select the relevant path and export it as DXF: - Go to "File > Save As...". - Choose "Desktop Cutting Plotter (AutoCAD DXF R12)(*.dxf)" format. diff --git a/src/preprocessing/geometries/io.jl b/src/preprocessing/geometries/io.jl index 1365a3bf21..d910e4ed6e 100644 --- a/src/preprocessing/geometries/io.jl +++ b/src/preprocessing/geometries/io.jl @@ -73,7 +73,8 @@ function load_dxf!(points::Vector{Tuple{T, T}}, filename) where {T} i += 1 end x1 = parse(T, strip(lines[i + 1])) - y1 = parse(T, strip(lines[i + 3])) # "20" is two lines further + @assert strip(lines[i + 2]) == "20" + y1 = parse(T, strip(lines[i + 3])) push!(points, (x1, y1)) end @@ -84,9 +85,10 @@ function load_dxf!(points::Vector{Tuple{T, T}}, filename) where {T} i += 1 end x2 = parse(T, strip(lines[i + 1])) - y2 = parse(T, strip(lines[i + 3])) # "21" is two lines further + @assert strip(lines[i + 2]) == "21" + y2 = parse(T, strip(lines[i + 3])) - # Add end points of the line to the point list + # Add end point of the line to the point list push!(points, (x2, y2)) end i += 1 @@ -105,7 +107,8 @@ function load_dxf!(points::Vector{Tuple{T, T}}, filename) where {T} i += 1 end x = parse(T, strip(lines[i + 1])) - y = parse(T, strip(lines[i + 3])) # "20" is two lines further + @assert strip(lines[i + 2]) == "20" + y = parse(T, strip(lines[i + 3])) # Add the vertex to the point list push!(points, (x, y))