Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bd38f6e3e | ||
|
|
9beacfe90e | ||
|
|
40458c6fa5 |
@@ -34,6 +34,7 @@ model/gltf gltf glb
|
||||
model/jt jt
|
||||
model/obj obj
|
||||
model/ply ply
|
||||
model/vnd.freecad fcstd FCStd
|
||||
model/shp shp
|
||||
model/step stp step
|
||||
model/stl stl
|
||||
|
||||
@@ -189,6 +189,7 @@ brlcad_add_test(NAME bu_file COMMAND bu_test test_file)
|
||||
|
||||
# NOTE: these test numbers correspond with the bu_mime_t enum values in include/bu/mime.h
|
||||
brlcad_add_test(NAME bu_file_mime_dxf COMMAND bu_test test_file_mime dxf 5 BU_MIME_MODEL_DXF) # tests dxf extension in model context
|
||||
brlcad_add_test(NAME bu_file_mime_fcstd COMMAND bu_test test_file_mime fcstd 5 BU_MIME_MODEL_VND_FREECAD) # tests fcstd extension in model context
|
||||
brlcad_add_test(NAME bu_file_mime_stp COMMAND bu_test test_file_mime stp 5 BU_MIME_MODEL_STEP) # tests stp extension in model context
|
||||
brlcad_add_test(NAME bu_file_mime_png COMMAND bu_test test_file_mime png 3 BU_MIME_IMAGE_PNG) # tests png extension in image context
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ add_subdirectory(asc)
|
||||
add_subdirectory(assetimport)
|
||||
add_subdirectory(decimate)
|
||||
add_subdirectory(fastgen4)
|
||||
add_subdirectory(freecad)
|
||||
add_subdirectory(gdal)
|
||||
add_subdirectory(gltf)
|
||||
add_subdirectory(iges)
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# FreeCAD FCStd importer/exporter plugin for libgcv.
|
||||
#
|
||||
# This plugin intentionally does not bundle OpenCASCADE. It is compiled only
|
||||
# when an external OpenCASCADE package, BRL-CAD's openNURBS, and zlib are
|
||||
# available. FCStd ZIP archive I/O uses the local zlib-backed helper in this
|
||||
# directory.
|
||||
|
||||
set(FREECAD_SRCS freecad_read.cpp freecad_write.cpp)
|
||||
set(FREECAD_EXTRA_SRCS
|
||||
fcstd_zip.cpp
|
||||
fcstd_zip.h
|
||||
open2open/include/open2open/brep_convert.h
|
||||
open2open/include/open2open/fcstd_convert.h
|
||||
open2open/include/open2open/geom_convert.h
|
||||
open2open/include/open2open/mesh_convert.h
|
||||
open2open/src/brep_convert.cpp
|
||||
open2open/src/fcstd_convert.cpp
|
||||
open2open/src/geom_convert.cpp
|
||||
open2open/src/mesh_convert.cpp
|
||||
)
|
||||
|
||||
set(_OPEN2OPEN_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/open2open")
|
||||
if(NOT EXISTS "${_OPEN2OPEN_SOURCE_DIR}/include/open2open/fcstd_convert.h" OR NOT EXISTS "${_OPEN2OPEN_SOURCE_DIR}/src/fcstd_convert.cpp")
|
||||
message(STATUS "gcv-freecad: local open2open source copy is incomplete - FreeCAD plugin disabled")
|
||||
cmakefiles(${FREECAD_SRCS} CMakeLists.txt)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(_FREECAD_OCCT_BUILD_TREE FALSE)
|
||||
if(OpenCASCADE_DIR)
|
||||
get_filename_component(_FREECAD_OCCT_CONFIG_DIR "${OpenCASCADE_DIR}" ABSOLUTE)
|
||||
get_filename_component(_FREECAD_OCCT_SOURCE_DIR "${_FREECAD_OCCT_CONFIG_DIR}" DIRECTORY)
|
||||
|
||||
if(EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/OpenCASCADEConfig.cmake"
|
||||
AND EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib"
|
||||
AND EXISTS "${_FREECAD_OCCT_SOURCE_DIR}/src/Standard/Standard.hxx")
|
||||
set(OpenCASCADE_FOUND TRUE)
|
||||
set(_FREECAD_OCCT_BUILD_TREE TRUE)
|
||||
set(OpenCASCADE_LIBRARY_DIR "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT OpenCASCADE_FOUND)
|
||||
find_package(OpenCASCADE CONFIG QUIET)
|
||||
if(NOT OpenCASCADE_FOUND)
|
||||
find_package(OpenCASCADE QUIET)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT OpenCASCADE_FOUND)
|
||||
message(STATUS "gcv-freecad: OpenCASCADE not found - FreeCAD plugin disabled")
|
||||
cmakefiles(${FREECAD_SRCS} CMakeLists.txt)
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(_FREECAD_ZLIB_LIB ZLIB::ZLIB)
|
||||
else()
|
||||
set(_FREECAD_ZLIB_LIB ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
brlcad_find_package(OPENNURBS REQUIRED SYSPATTERN openNURBS)
|
||||
if(NOT TARGET OPENNURBS::OPENNURBS)
|
||||
message(STATUS "gcv-freecad: BRL-CAD openNURBS target not found - FreeCAD plugin disabled")
|
||||
cmakefiles(${FREECAD_SRCS} CMakeLists.txt)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(_FREECAD_OCCT_LIBS
|
||||
TKernel
|
||||
TKMath
|
||||
TKG2d
|
||||
TKG3d
|
||||
TKGeomBase
|
||||
TKBRep
|
||||
TKGeomAlgo
|
||||
TKTopAlgo
|
||||
TKCDF
|
||||
)
|
||||
|
||||
set(_FREECAD_OCCT_INCLUDE_DIRS ${OpenCASCADE_INCLUDE_DIR})
|
||||
set(_FREECAD_OCCT_LIBRARY_DIRS ${OpenCASCADE_LIBRARY_DIR})
|
||||
|
||||
if(OpenCASCADE_DIR)
|
||||
get_filename_component(_FREECAD_OCCT_CONFIG_DIR "${OpenCASCADE_DIR}" ABSOLUTE)
|
||||
|
||||
if(EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib")
|
||||
list(APPEND _FREECAD_OCCT_LIBRARY_DIRS "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/inc")
|
||||
list(APPEND _FREECAD_OCCT_INCLUDE_DIRS "${_FREECAD_OCCT_CONFIG_DIR}/inc")
|
||||
endif()
|
||||
|
||||
get_filename_component(_FREECAD_OCCT_SOURCE_DIR "${_FREECAD_OCCT_CONFIG_DIR}" DIRECTORY)
|
||||
if(EXISTS "${_FREECAD_OCCT_SOURCE_DIR}/src/Standard/Standard.hxx")
|
||||
file(GLOB _FREECAD_OCCT_SRC_INCLUDE_DIRS LIST_DIRECTORIES true "${_FREECAD_OCCT_SOURCE_DIR}/src/*")
|
||||
list(APPEND _FREECAD_OCCT_INCLUDE_DIRS ${_FREECAD_OCCT_SRC_INCLUDE_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
list(REMOVE_DUPLICATES _FREECAD_OCCT_INCLUDE_DIRS)
|
||||
list(REMOVE_DUPLICATES _FREECAD_OCCT_LIBRARY_DIRS)
|
||||
|
||||
set(_FREECAD_OPEN2OPEN_SRCS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/fcstd_zip.cpp"
|
||||
"${_OPEN2OPEN_SOURCE_DIR}/src/geom_convert.cpp"
|
||||
"${_OPEN2OPEN_SOURCE_DIR}/src/brep_convert.cpp"
|
||||
"${_OPEN2OPEN_SOURCE_DIR}/src/mesh_convert.cpp"
|
||||
"${_OPEN2OPEN_SOURCE_DIR}/src/fcstd_convert.cpp"
|
||||
)
|
||||
|
||||
add_library(gcv_freecad_open2open OBJECT ${_FREECAD_OPEN2OPEN_SRCS})
|
||||
target_include_directories(gcv_freecad_open2open
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${_OPEN2OPEN_SOURCE_DIR}/include"
|
||||
"${_OPEN2OPEN_SOURCE_DIR}/compat"
|
||||
${_FREECAD_OCCT_INCLUDE_DIRS}
|
||||
)
|
||||
target_compile_definitions(gcv_freecad_open2open PUBLIC OPEN2OPEN_HAVE_LIBZIP)
|
||||
target_link_libraries(gcv_freecad_open2open PUBLIC OPENNURBS::OPENNURBS ${_FREECAD_ZLIB_LIB})
|
||||
target_compile_options(gcv_freecad_open2open PRIVATE $<$<CXX_COMPILER_ID:GNU,Clang>:-w>)
|
||||
|
||||
message(STATUS "gcv-freecad: FreeCAD plugin enabled using open2open at ${_OPEN2OPEN_SOURCE_DIR}")
|
||||
|
||||
gcv_plugin_library(gcv-freecad SHARED ${FREECAD_SRCS})
|
||||
target_include_directories(gcv-freecad PRIVATE
|
||||
$<TARGET_PROPERTY:gcv_freecad_open2open,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
)
|
||||
target_compile_definitions(gcv-freecad PRIVATE BRLCADBUILD HAVE_CONFIG_H OPEN2OPEN_HAVE_LIBZIP)
|
||||
if(_FREECAD_OCCT_LIBRARY_DIRS)
|
||||
target_link_directories(gcv-freecad PRIVATE ${_FREECAD_OCCT_LIBRARY_DIRS})
|
||||
endif()
|
||||
target_link_libraries(gcv-freecad
|
||||
libgcv
|
||||
librt
|
||||
libwdb
|
||||
libbg
|
||||
libbu
|
||||
OPENNURBS::OPENNURBS
|
||||
$<TARGET_OBJECTS:gcv_freecad_open2open>
|
||||
${_FREECAD_OCCT_LIBS}
|
||||
${_FREECAD_ZLIB_LIB}
|
||||
)
|
||||
validate_style(gcv-freecad "${FREECAD_SRCS}")
|
||||
plugin_setup(gcv-freecad gcv)
|
||||
|
||||
cmakefiles(
|
||||
${FREECAD_SRCS}
|
||||
${FREECAD_EXTRA_SRCS}
|
||||
CMakeLists.txt
|
||||
)
|
||||
|
||||
# Local Variables:
|
||||
# tab-width: 8
|
||||
# mode: cmake
|
||||
# indent-tabs-mode: t
|
||||
# End:
|
||||
# ex: shiftwidth=2 tabstop=8
|
||||
@@ -0,0 +1,427 @@
|
||||
#include "fcstd_zip.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct zip_entry {
|
||||
std::string name;
|
||||
std::uint16_t method = 0;
|
||||
std::uint32_t crc = 0;
|
||||
std::uint32_t compressed_size = 0;
|
||||
std::uint32_t uncompressed_size = 0;
|
||||
std::uint32_t local_header_offset = 0;
|
||||
std::vector<unsigned char> compressed;
|
||||
std::vector<unsigned char> uncompressed;
|
||||
};
|
||||
|
||||
struct zip {
|
||||
std::string path;
|
||||
bool write_mode = false;
|
||||
std::map<std::string, zip_entry> entries;
|
||||
};
|
||||
|
||||
struct zip_file {
|
||||
std::vector<unsigned char> data;
|
||||
std::size_t offset = 0;
|
||||
};
|
||||
|
||||
struct zip_source {
|
||||
std::vector<unsigned char> data;
|
||||
};
|
||||
|
||||
static std::uint16_t
|
||||
read_le16(const unsigned char *p)
|
||||
{
|
||||
return (std::uint16_t)p[0] | ((std::uint16_t)p[1] << 8);
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
read_le32(const unsigned char *p)
|
||||
{
|
||||
return (std::uint32_t)p[0] |
|
||||
((std::uint32_t)p[1] << 8) |
|
||||
((std::uint32_t)p[2] << 16) |
|
||||
((std::uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static void
|
||||
append_le16(std::vector<unsigned char> &out, std::uint16_t v)
|
||||
{
|
||||
out.push_back((unsigned char)(v & 0xff));
|
||||
out.push_back((unsigned char)((v >> 8) & 0xff));
|
||||
}
|
||||
|
||||
static void
|
||||
append_le32(std::vector<unsigned char> &out, std::uint32_t v)
|
||||
{
|
||||
out.push_back((unsigned char)(v & 0xff));
|
||||
out.push_back((unsigned char)((v >> 8) & 0xff));
|
||||
out.push_back((unsigned char)((v >> 16) & 0xff));
|
||||
out.push_back((unsigned char)((v >> 24) & 0xff));
|
||||
}
|
||||
|
||||
static bool
|
||||
read_file(const std::string &path, std::vector<unsigned char> &data)
|
||||
{
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
if (!in)
|
||||
return false;
|
||||
in.seekg(0, std::ios::end);
|
||||
std::streamoff sz = in.tellg();
|
||||
if (sz < 0)
|
||||
return false;
|
||||
in.seekg(0, std::ios::beg);
|
||||
data.resize((std::size_t)sz);
|
||||
if (!data.empty())
|
||||
in.read((char *)data.data(), (std::streamsize)data.size());
|
||||
return in.good() || in.eof();
|
||||
}
|
||||
|
||||
static bool
|
||||
inflate_raw(const std::vector<unsigned char> &input, std::uint32_t expected_size, std::vector<unsigned char> &output)
|
||||
{
|
||||
output.assign(expected_size, 0);
|
||||
unsigned char empty_output = 0;
|
||||
|
||||
z_stream zs;
|
||||
std::memset(&zs, 0, sizeof(zs));
|
||||
zs.next_in = const_cast<Bytef *>(input.data());
|
||||
zs.avail_in = (uInt)input.size();
|
||||
zs.next_out = expected_size ? output.data() : &empty_output;
|
||||
zs.avail_out = expected_size ? (uInt)output.size() : 1;
|
||||
|
||||
if (inflateInit2(&zs, -MAX_WBITS) != Z_OK)
|
||||
return false;
|
||||
int ret = inflate(&zs, Z_FINISH);
|
||||
inflateEnd(&zs);
|
||||
return ret == Z_STREAM_END && zs.total_out == expected_size;
|
||||
}
|
||||
|
||||
static bool
|
||||
deflate_raw(const std::vector<unsigned char> &input, std::vector<unsigned char> &output)
|
||||
{
|
||||
z_stream zs;
|
||||
std::memset(&zs, 0, sizeof(zs));
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
return false;
|
||||
|
||||
uLong bound = deflateBound(&zs, (uLong)input.size());
|
||||
output.assign((std::size_t)bound, 0);
|
||||
zs.next_in = const_cast<Bytef *>(input.data());
|
||||
zs.avail_in = (uInt)input.size();
|
||||
zs.next_out = output.data();
|
||||
zs.avail_out = (uInt)output.size();
|
||||
|
||||
int ret = deflate(&zs, Z_FINISH);
|
||||
if (ret != Z_STREAM_END) {
|
||||
deflateEnd(&zs);
|
||||
return false;
|
||||
}
|
||||
output.resize((std::size_t)zs.total_out);
|
||||
deflateEnd(&zs);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_zip(zip_t *archive)
|
||||
{
|
||||
std::vector<unsigned char> file_data;
|
||||
if (!read_file(archive->path, file_data) || file_data.size() < 22)
|
||||
return false;
|
||||
|
||||
std::size_t scan_start = file_data.size() > 66000 ? file_data.size() - 66000 : 0;
|
||||
std::size_t eocd = std::string::npos;
|
||||
for (std::size_t i = file_data.size() - 22; i + 1 > scan_start; --i) {
|
||||
if (read_le32(&file_data[i]) == 0x06054b50u) {
|
||||
eocd = i;
|
||||
break;
|
||||
}
|
||||
if (i == 0)
|
||||
break;
|
||||
}
|
||||
if (eocd == std::string::npos)
|
||||
return false;
|
||||
|
||||
std::uint16_t total_entries = read_le16(&file_data[eocd + 10]);
|
||||
std::uint32_t cd_size = read_le32(&file_data[eocd + 12]);
|
||||
std::uint32_t cd_offset = read_le32(&file_data[eocd + 16]);
|
||||
if ((std::uint64_t)cd_offset + cd_size > file_data.size())
|
||||
return false;
|
||||
|
||||
std::size_t pos = cd_offset;
|
||||
for (std::uint16_t i = 0; i < total_entries; ++i) {
|
||||
if (pos + 46 > file_data.size() || read_le32(&file_data[pos]) != 0x02014b50u)
|
||||
return false;
|
||||
|
||||
std::uint16_t flags = read_le16(&file_data[pos + 8]);
|
||||
std::uint16_t method = read_le16(&file_data[pos + 10]);
|
||||
std::uint32_t crc = read_le32(&file_data[pos + 16]);
|
||||
std::uint32_t csize = read_le32(&file_data[pos + 20]);
|
||||
std::uint32_t usize = read_le32(&file_data[pos + 24]);
|
||||
std::uint16_t name_len = read_le16(&file_data[pos + 28]);
|
||||
std::uint16_t extra_len = read_le16(&file_data[pos + 30]);
|
||||
std::uint16_t comment_len = read_le16(&file_data[pos + 32]);
|
||||
std::uint32_t local_offset = read_le32(&file_data[pos + 42]);
|
||||
|
||||
if ((flags & 1) || (method != 0 && method != 8))
|
||||
return false;
|
||||
if (pos + 46u + name_len + extra_len + comment_len > file_data.size())
|
||||
return false;
|
||||
|
||||
std::string name((const char *)&file_data[pos + 46], name_len);
|
||||
if ((std::uint64_t)local_offset + 30 > file_data.size() ||
|
||||
read_le32(&file_data[local_offset]) != 0x04034b50u)
|
||||
return false;
|
||||
|
||||
std::uint16_t lname_len = read_le16(&file_data[local_offset + 26]);
|
||||
std::uint16_t lextra_len = read_le16(&file_data[local_offset + 28]);
|
||||
std::uint64_t data_offset = (std::uint64_t)local_offset + 30 + lname_len + lextra_len;
|
||||
if (data_offset + csize > file_data.size())
|
||||
return false;
|
||||
|
||||
zip_entry e;
|
||||
e.name = name;
|
||||
e.method = method;
|
||||
e.crc = crc;
|
||||
e.compressed_size = csize;
|
||||
e.uncompressed_size = usize;
|
||||
e.local_header_offset = local_offset;
|
||||
e.compressed.assign(file_data.begin() + (std::ptrdiff_t)data_offset,
|
||||
file_data.begin() + (std::ptrdiff_t)(data_offset + csize));
|
||||
|
||||
archive->entries[name] = std::move(e);
|
||||
pos += 46u + name_len + extra_len + comment_len;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" zip_t *
|
||||
zip_open(const char *path, int flags, int *errorp)
|
||||
{
|
||||
if (errorp)
|
||||
*errorp = 0;
|
||||
if (!path)
|
||||
return nullptr;
|
||||
|
||||
zip_t *archive = new zip;
|
||||
archive->path = path;
|
||||
archive->write_mode = (flags & ZIP_CREATE) || (flags & ZIP_TRUNCATE);
|
||||
|
||||
if (!archive->write_mode) {
|
||||
if (!parse_zip(archive)) {
|
||||
delete archive;
|
||||
if (errorp)
|
||||
*errorp = 1;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
zip_discard(zip_t *archive)
|
||||
{
|
||||
delete archive;
|
||||
}
|
||||
|
||||
extern "C" zip_file_t *
|
||||
zip_fopen(zip_t *archive, const char *fname, int)
|
||||
{
|
||||
if (!archive || !fname)
|
||||
return nullptr;
|
||||
auto it = archive->entries.find(fname);
|
||||
if (it == archive->entries.end())
|
||||
return nullptr;
|
||||
|
||||
zip_file_t *f = new zip_file;
|
||||
const zip_entry &entry = it->second;
|
||||
if (!entry.uncompressed.empty() || entry.uncompressed_size == 0) {
|
||||
f->data = entry.uncompressed;
|
||||
} else if (entry.method == 0) {
|
||||
f->data = entry.compressed;
|
||||
if (f->data.size() != entry.uncompressed_size) {
|
||||
delete f;
|
||||
return nullptr;
|
||||
}
|
||||
} else if (!inflate_raw(entry.compressed, entry.uncompressed_size, f->data)) {
|
||||
delete f;
|
||||
return nullptr;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
extern "C" zip_int64_t
|
||||
zip_fread(zip_file_t *file, void *buf, zip_uint64_t nbytes)
|
||||
{
|
||||
if (!file || !buf)
|
||||
return -1;
|
||||
std::size_t remaining = file->data.size() - file->offset;
|
||||
std::size_t n = std::min<std::size_t>((std::size_t)nbytes, remaining);
|
||||
if (n)
|
||||
std::memcpy(buf, file->data.data() + file->offset, n);
|
||||
file->offset += n;
|
||||
return (zip_int64_t)n;
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
zip_fclose(zip_file_t *file)
|
||||
{
|
||||
delete file;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
zip_stat_init(zip_stat_t *st)
|
||||
{
|
||||
if (st)
|
||||
std::memset(st, 0, sizeof(*st));
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
zip_stat(zip_t *archive, const char *fname, int, zip_stat_t *st)
|
||||
{
|
||||
if (!archive || !fname || !st)
|
||||
return -1;
|
||||
auto it = archive->entries.find(fname);
|
||||
if (it == archive->entries.end())
|
||||
return -1;
|
||||
zip_stat_init(st);
|
||||
st->valid = ZIP_STAT_SIZE;
|
||||
st->size = it->second.uncompressed_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" zip_source_t *
|
||||
zip_source_buffer(zip_t *, const void *data, zip_uint64_t len, int)
|
||||
{
|
||||
if (!data && len)
|
||||
return nullptr;
|
||||
zip_source_t *src = new zip_source;
|
||||
const unsigned char *bytes = (const unsigned char *)data;
|
||||
src->data.assign(bytes, bytes + len);
|
||||
return src;
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
zip_source_free(zip_source_t *source)
|
||||
{
|
||||
delete source;
|
||||
}
|
||||
|
||||
extern "C" zip_int64_t
|
||||
zip_file_add(zip_t *archive, const char *name, zip_source_t *source, int)
|
||||
{
|
||||
if (!archive || !archive->write_mode || !name || !source)
|
||||
return -1;
|
||||
|
||||
zip_entry e;
|
||||
e.name = name;
|
||||
e.uncompressed = source->data;
|
||||
e.uncompressed_size = (std::uint32_t)e.uncompressed.size();
|
||||
e.crc = crc32(0L, Z_NULL, 0);
|
||||
if (!e.uncompressed.empty())
|
||||
e.crc = crc32(e.crc, e.uncompressed.data(), (uInt)e.uncompressed.size());
|
||||
|
||||
std::vector<unsigned char> compressed;
|
||||
if (deflate_raw(e.uncompressed, compressed) && compressed.size() < e.uncompressed.size()) {
|
||||
e.method = 8;
|
||||
e.compressed = std::move(compressed);
|
||||
} else {
|
||||
e.method = 0;
|
||||
e.compressed = e.uncompressed;
|
||||
}
|
||||
e.compressed_size = (std::uint32_t)e.compressed.size();
|
||||
|
||||
archive->entries[e.name] = std::move(e);
|
||||
delete source;
|
||||
return (zip_int64_t)archive->entries.size() - 1;
|
||||
}
|
||||
|
||||
static bool
|
||||
write_all(std::ofstream &out, const std::vector<unsigned char> &data)
|
||||
{
|
||||
if (!data.empty())
|
||||
out.write((const char *)data.data(), (std::streamsize)data.size());
|
||||
return out.good();
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
zip_close(zip_t *archive)
|
||||
{
|
||||
if (!archive)
|
||||
return -1;
|
||||
if (!archive->write_mode) {
|
||||
delete archive;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> file_data;
|
||||
std::vector<unsigned char> central_dir;
|
||||
|
||||
for (auto &kv : archive->entries) {
|
||||
zip_entry &e = kv.second;
|
||||
e.local_header_offset = (std::uint32_t)file_data.size();
|
||||
|
||||
append_le32(file_data, 0x04034b50u);
|
||||
append_le16(file_data, 20);
|
||||
append_le16(file_data, 0x0800u);
|
||||
append_le16(file_data, e.method);
|
||||
append_le16(file_data, 0);
|
||||
append_le16(file_data, 0);
|
||||
append_le32(file_data, e.crc);
|
||||
append_le32(file_data, e.compressed_size);
|
||||
append_le32(file_data, e.uncompressed_size);
|
||||
append_le16(file_data, (std::uint16_t)e.name.size());
|
||||
append_le16(file_data, 0);
|
||||
file_data.insert(file_data.end(), e.name.begin(), e.name.end());
|
||||
file_data.insert(file_data.end(), e.compressed.begin(), e.compressed.end());
|
||||
|
||||
append_le32(central_dir, 0x02014b50u);
|
||||
append_le16(central_dir, 20);
|
||||
append_le16(central_dir, 20);
|
||||
append_le16(central_dir, 0x0800u);
|
||||
append_le16(central_dir, e.method);
|
||||
append_le16(central_dir, 0);
|
||||
append_le16(central_dir, 0);
|
||||
append_le32(central_dir, e.crc);
|
||||
append_le32(central_dir, e.compressed_size);
|
||||
append_le32(central_dir, e.uncompressed_size);
|
||||
append_le16(central_dir, (std::uint16_t)e.name.size());
|
||||
append_le16(central_dir, 0);
|
||||
append_le16(central_dir, 0);
|
||||
append_le16(central_dir, 0);
|
||||
append_le16(central_dir, 0);
|
||||
append_le32(central_dir, 0);
|
||||
append_le32(central_dir, e.local_header_offset);
|
||||
central_dir.insert(central_dir.end(), e.name.begin(), e.name.end());
|
||||
}
|
||||
|
||||
std::uint32_t cd_offset = (std::uint32_t)file_data.size();
|
||||
std::uint32_t cd_size = (std::uint32_t)central_dir.size();
|
||||
file_data.insert(file_data.end(), central_dir.begin(), central_dir.end());
|
||||
|
||||
append_le32(file_data, 0x06054b50u);
|
||||
append_le16(file_data, 0);
|
||||
append_le16(file_data, 0);
|
||||
append_le16(file_data, (std::uint16_t)archive->entries.size());
|
||||
append_le16(file_data, (std::uint16_t)archive->entries.size());
|
||||
append_le32(file_data, cd_size);
|
||||
append_le32(file_data, cd_offset);
|
||||
append_le16(file_data, 0);
|
||||
|
||||
std::ofstream out(archive->path, std::ios::binary | std::ios::trunc);
|
||||
bool ok = out && write_all(out, file_data);
|
||||
out.close();
|
||||
delete archive;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Minimal FCStd ZIP archive interface for the local open2open copy.
|
||||
*
|
||||
* This intentionally implements only the archive operations used by
|
||||
* open2open/src/fcstd_convert.cpp, using zlib for ZIP deflate streams.
|
||||
*/
|
||||
|
||||
#ifndef GCV_FREECAD_FCSTD_ZIP_H
|
||||
#define GCV_FREECAD_FCSTD_ZIP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int64_t zip_int64_t;
|
||||
typedef uint64_t zip_uint64_t;
|
||||
|
||||
typedef struct zip zip_t;
|
||||
typedef struct zip_file zip_file_t;
|
||||
typedef struct zip_source zip_source_t;
|
||||
|
||||
typedef struct zip_stat {
|
||||
zip_uint64_t valid;
|
||||
zip_uint64_t size;
|
||||
} zip_stat_t;
|
||||
|
||||
#define ZIP_RDONLY 0
|
||||
#define ZIP_CREATE 1
|
||||
#define ZIP_TRUNCATE 8
|
||||
#define ZIP_FL_OVERWRITE 8192
|
||||
#define ZIP_STAT_SIZE 0x0004u
|
||||
|
||||
zip_t *zip_open(const char *path, int flags, int *errorp);
|
||||
int zip_close(zip_t *archive);
|
||||
void zip_discard(zip_t *archive);
|
||||
|
||||
zip_file_t *zip_fopen(zip_t *archive, const char *fname, int flags);
|
||||
zip_int64_t zip_fread(zip_file_t *file, void *buf, zip_uint64_t nbytes);
|
||||
int zip_fclose(zip_file_t *file);
|
||||
|
||||
void zip_stat_init(zip_stat_t *st);
|
||||
int zip_stat(zip_t *archive, const char *fname, int flags, zip_stat_t *st);
|
||||
|
||||
zip_source_t *zip_source_buffer(zip_t *archive, const void *data, zip_uint64_t len, int freep);
|
||||
zip_int64_t zip_file_add(zip_t *archive, const char *name, zip_source_t *source, int flags);
|
||||
void zip_source_free(zip_source_t *source);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
FreeCAD GCV plugin improvement targets
|
||||
======================================
|
||||
|
||||
Current measured baseline
|
||||
-------------------------
|
||||
|
||||
Sample: (https://github.com/starseeker/open2open)
|
||||
|
||||
open2open/phasma-mech-assembly/FreeCAD native models/Simple structure w. panels.FCStd
|
||||
|
||||
Recent full import result:
|
||||
.build/bin/gcv -i <sample.FCStd> -o /tmp/brlcad_freecad_simple_optimized.g
|
||||
real 70.64s
|
||||
output size 276M
|
||||
|
||||
With GCV_FREECAD_TIMING=1:
|
||||
importer total_ms=47456
|
||||
wall real=69.89s
|
||||
converted=196
|
||||
hidden=107
|
||||
cached=64
|
||||
conversion_failed=2
|
||||
|
||||
This means roughly 22s of the wall time is outside the measured FreeCAD
|
||||
reader/converter path, most likely in downstream .g serialization.
|
||||
|
||||
Completed performance fix
|
||||
-------------------------
|
||||
|
||||
OCCTToON_Brep loop-gap repair previously rebuilt a pcurve reference-count map
|
||||
inside every loop by scanning all trims created so far. On large BReps this was
|
||||
effectively O(loop count * trim count). It has been replaced with an incremental
|
||||
trim_curve_refcount map.
|
||||
|
||||
Measured example:
|
||||
b_ClydeSpace_30Wh_battery_pack__simplified__001_
|
||||
before: convert_ms=14486, pass3_loop_gap_us=13812995
|
||||
after: convert_ms=689, pass3_loop_gap_us=18837
|
||||
|
||||
Next targets
|
||||
------------
|
||||
|
||||
1. Attribute the post-import .g writing cost.
|
||||
|
||||
The timed importer reports about 47.5s, while the complete gcv run takes
|
||||
about 69.9s. Add or reuse timing around the GCV/database output path to
|
||||
identify whether the remaining ~22s is object insertion, directory updates,
|
||||
final db close/write, or BRep serialization.
|
||||
|
||||
2. Investigate large FeaturePython BRep entries.
|
||||
|
||||
The largest remaining successful object cost in the sample is:
|
||||
b_CubeADCS_Gen_2_001_
|
||||
FCStd entry size: 62631280 bytes
|
||||
faces: 28576
|
||||
read_ms=1122
|
||||
brep_read_ms=2500
|
||||
convert_ms=7659
|
||||
add_ms=289
|
||||
|
||||
Similar large objects dominate the importer time. The immediate work is to
|
||||
profile these individually and decide whether the cost is unavoidable geometry
|
||||
size or whether more caching/instancing is possible.
|
||||
|
||||
3. Test file-backed OCCT BRep reads for large BRP payloads.
|
||||
|
||||
Current path reads BRP content from the FCStd zip into memory and then uses an
|
||||
in-memory stream for BRepTools::Read. For tens-of-megabytes BRP payloads,
|
||||
test extracting to a temporary file and using the filename-based OCCT read
|
||||
API. Compare read_ms and brep_read_ms on CubeADCS-scale entries.
|
||||
|
||||
4. Avoid converting repeated assembly instances as full standalone BReps.
|
||||
|
||||
Exact BRP text caching helps when the payloads are byte-identical, but many
|
||||
assembly repetitions differ by placement or generated payload. If FreeCAD
|
||||
object placement/link metadata can be mapped to BRL-CAD comb instances with
|
||||
transforms, this would reduce both conversion time and .g size. This is
|
||||
likely the largest architectural improvement.
|
||||
|
||||
5. Handle or cheaply reject expensive conversion failures.
|
||||
|
||||
In the sample, two large antenna mechanism objects failed after expensive
|
||||
parse/conversion work:
|
||||
b_Phasma_Primus_Antenna_Deployment_Mechanism__001_001
|
||||
read_ms=708, brep_read_ms=1456, convert_ms=3192
|
||||
b_Phasma_Primus_Antenna_Deployment_Mechanism__001_002
|
||||
read_ms=721, brep_read_ms=1488, convert_ms=3214
|
||||
|
||||
Determine why OCCTToON_Brep returns invalid for these and either fix the
|
||||
conversion or add a cheaper preflight/skip path if they are out of scope.
|
||||
|
||||
6. Further split remaining Pass 3 face conversion time if needed.
|
||||
|
||||
After the loop-gap refcount fix, pass3_loop_gap_us is no longer dominant.
|
||||
Remaining Pass 3 time is mostly surface conversion, pcurve conversion, and
|
||||
miscellaneous face/loop/trim construction. If #1-#5 are exhausted, add finer
|
||||
counters around NewFace/NewLoop/NewTrim, wire traversal, pcurve domain
|
||||
remapping, and surface cache hits/misses.
|
||||
|
||||
7. Keep diagnostics gated.
|
||||
|
||||
GCV_FREECAD_TIMING is the current opt-in switch for detailed importer and
|
||||
OCCTToON_Brep timings. Any additional instrumentation should stay behind this
|
||||
environment variable, especially if placed inside face/edge/trim loops.
|
||||
@@ -0,0 +1,241 @@
|
||||
/* F R E E C A D _ R E A D . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
/** @file freecad_read.cpp
|
||||
*
|
||||
* Import FreeCAD .FCStd files into BRL-CAD using open2open's FCStd reader and
|
||||
* OCCT -> openNURBS BRep conversion core.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "bu/path.h"
|
||||
#include "gcv/api.h"
|
||||
#include "wdb.h"
|
||||
|
||||
#include "opennurbs.h"
|
||||
#include "open2open/fcstd_convert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace freecad_read
|
||||
{
|
||||
|
||||
|
||||
static std::string
|
||||
clean_name(const std::string &raw, const char *fallback, int index)
|
||||
{
|
||||
std::string name = raw.empty() ? std::string(fallback) + "_" + std::to_string(index) : raw;
|
||||
|
||||
for (std::string::iterator it = name.begin(); it != name.end(); ++it) {
|
||||
unsigned char c = static_cast<unsigned char>(*it);
|
||||
if (!std::isalnum(c) && *it != '_' && *it != '-' && *it != '.')
|
||||
*it = '_';
|
||||
}
|
||||
|
||||
if (name.empty())
|
||||
name = std::string(fallback) + "_" + std::to_string(index);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
static std::string
|
||||
unique_name(const std::string &base, std::map<std::string, int> &used)
|
||||
{
|
||||
std::map<std::string, int>::iterator found = used.find(base);
|
||||
if (found == used.end()) {
|
||||
used[base] = 1;
|
||||
return base;
|
||||
}
|
||||
|
||||
int next = ++found->second;
|
||||
return base + "_" + std::to_string(next);
|
||||
}
|
||||
|
||||
|
||||
static std::string
|
||||
root_name_from_path(const char *source_path, const gcv_opts *gcv_options)
|
||||
{
|
||||
if (gcv_options && gcv_options->default_name)
|
||||
return clean_name(gcv_options->default_name, "freecad_model", 0);
|
||||
|
||||
bu_vls basename = BU_VLS_INIT_ZERO;
|
||||
if (source_path && bu_path_component(&basename, source_path, BU_PATH_BASENAME_EXTLESS)) {
|
||||
std::string result = clean_name(bu_vls_cstr(&basename), "freecad_model", 0);
|
||||
bu_vls_free(&basename);
|
||||
return result;
|
||||
}
|
||||
|
||||
bu_vls_free(&basename);
|
||||
return "freecad_model";
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_color_attribute(struct rt_wdb *wdbp, const std::string &name, const ON_3dmObjectAttributes *attrs)
|
||||
{
|
||||
if (!wdbp || !attrs)
|
||||
return;
|
||||
|
||||
ON_Color c = attrs->m_color;
|
||||
if (c == ON_Color::UnsetColor)
|
||||
return;
|
||||
|
||||
bu_vls rgb = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&rgb, "%d/%d/%d", c.Red(), c.Green(), c.Blue());
|
||||
(void)db5_update_attribute(name.c_str(), "color", bu_vls_cstr(&rgb), wdbp->dbip);
|
||||
bu_vls_free(&rgb);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
freecad_read(gcv_context *context, const gcv_opts *gcv_options, const void *UNUSED(options_data), const char *source_path)
|
||||
{
|
||||
if (!context || !context->dbip || !source_path)
|
||||
return 0;
|
||||
|
||||
ONX_Model model;
|
||||
int converted = open2open::FCStdFileToONX_Model(source_path, model);
|
||||
if (converted <= 0) {
|
||||
bu_log("freecad_read: no FreeCAD shapes were converted from %s\n", source_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct rt_wdb *wdbp = wdb_dbopen(context->dbip, RT_WDB_TYPE_DB_INMEM);
|
||||
if (!wdbp)
|
||||
return 0;
|
||||
|
||||
std::map<std::string, int> used;
|
||||
std::vector<std::string> solids;
|
||||
|
||||
ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry);
|
||||
int index = 0;
|
||||
for (const ON_ModelComponent *component = it.FirstComponent(); component; component = it.NextComponent(), ++index) {
|
||||
const ON_ModelGeometryComponent *geometry_component = ON_ModelGeometryComponent::Cast(component);
|
||||
if (!geometry_component)
|
||||
continue;
|
||||
|
||||
const ON_Geometry *geometry = geometry_component->Geometry(NULL);
|
||||
if (!geometry)
|
||||
continue;
|
||||
|
||||
std::unique_ptr<ON_Brep> brep_holder;
|
||||
const ON_Brep *brep = ON_Brep::Cast(geometry);
|
||||
if (!brep && geometry->HasBrepForm()) {
|
||||
brep_holder.reset(geometry->BrepForm());
|
||||
brep = brep_holder.get();
|
||||
}
|
||||
|
||||
if (!brep)
|
||||
continue;
|
||||
|
||||
if (brep->m_F.Count() <= 0)
|
||||
continue;
|
||||
|
||||
std::string raw_name;
|
||||
const ON_3dmObjectAttributes *attrs = geometry_component->Attributes(NULL);
|
||||
if (attrs && attrs->m_name.IsNotEmpty()) {
|
||||
ON_String name_utf8(attrs->m_name);
|
||||
raw_name = name_utf8.Array();
|
||||
}
|
||||
|
||||
std::string solid_name = unique_name(clean_name(raw_name, "freecad_shape", index), used);
|
||||
ON_Brep *writable_brep = const_cast<ON_Brep *>(brep);
|
||||
if (mk_brep(wdbp, solid_name.c_str(), (void *)writable_brep)) {
|
||||
bu_log("freecad_read: failed to write BRep %s\n", solid_name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)db5_update_attribute(solid_name.c_str(), "importer", "gcv-freecad", wdbp->dbip);
|
||||
add_color_attribute(wdbp, solid_name, attrs);
|
||||
solids.push_back(solid_name);
|
||||
}
|
||||
|
||||
if (solids.empty()) {
|
||||
bu_log("freecad_read: no BRep solids were written for %s\n", source_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct wmember root;
|
||||
BU_LIST_INIT(&root.l);
|
||||
for (std::vector<std::string>::const_iterator s = solids.begin(); s != solids.end(); ++s)
|
||||
mk_addmember(s->c_str(), &root.l, NULL, WMOP_UNION);
|
||||
|
||||
std::string root_name = unique_name(root_name_from_path(source_path, gcv_options), used);
|
||||
if (mk_comb(wdbp, root_name.c_str(), &root.l, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0))
|
||||
bu_log("freecad_read: failed to write top-level combination %s\n", root_name.c_str());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
freecad_can_read(const char *source_path)
|
||||
{
|
||||
if (!source_path)
|
||||
return 0;
|
||||
|
||||
const char *ext = strrchr(source_path, '.');
|
||||
if (ext && BU_STR_EQUIV(ext + 1, "fcstd"))
|
||||
return 1;
|
||||
|
||||
open2open::FcstdDoc doc;
|
||||
return open2open::ReadFcstdDoc(source_path, doc) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct gcv_filter gcv_conv_freecad_read = {
|
||||
"FreeCAD Reader", GCV_FILTER_READ, BU_MIME_MODEL_VND_FREECAD, freecad_can_read,
|
||||
NULL, NULL, freecad_read
|
||||
};
|
||||
|
||||
|
||||
extern "C" const struct gcv_filter gcv_conv_freecad_write;
|
||||
static const struct gcv_filter * const filters[] = {&gcv_conv_freecad_read, &gcv_conv_freecad_write, NULL};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
{
|
||||
extern const gcv_plugin gcv_plugin_info_s = {freecad_read::filters};
|
||||
COMPILER_DLLEXPORT const struct gcv_plugin *
|
||||
gcv_plugin_info()
|
||||
{
|
||||
return &gcv_plugin_info_s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Local Variables:
|
||||
// tab-width: 8
|
||||
// mode: C++
|
||||
// c-basic-offset: 4
|
||||
// indent-tabs-mode: t
|
||||
// c-file-style: "stroustrup"
|
||||
// End:
|
||||
// ex: shiftwidth=4 tabstop=8
|
||||
@@ -0,0 +1,237 @@
|
||||
/* F R E E C A D _ W R I T E . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
/** @file freecad_write.cpp
|
||||
*
|
||||
* Export BRL-CAD BRep primitives to FreeCAD .FCStd using open2open's
|
||||
* openNURBS -> OCCT writer.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "bu/path.h"
|
||||
#include "gcv/api.h"
|
||||
#include "raytrace.h"
|
||||
#include "rt/comb.h"
|
||||
#include "rt/geom.h"
|
||||
|
||||
#include "opennurbs.h"
|
||||
#include "open2open/fcstd_convert.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace freecad_write
|
||||
{
|
||||
|
||||
|
||||
static std::string
|
||||
clean_name(const char *raw, const char *fallback, int index)
|
||||
{
|
||||
std::string name = (raw && raw[0]) ? raw : std::string(fallback) + "_" + std::to_string(index);
|
||||
|
||||
for (std::string::iterator it = name.begin(); it != name.end(); ++it) {
|
||||
unsigned char c = static_cast<unsigned char>(*it);
|
||||
if (!std::isalnum(c) && *it != '_' && *it != '-' && *it != '.')
|
||||
*it = '_';
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
append_brep(struct db_i *dbip, const struct directory *dp, ONX_Model &model, int index)
|
||||
{
|
||||
if (!dbip || !dp)
|
||||
return false;
|
||||
|
||||
struct rt_db_internal intern;
|
||||
RT_DB_INTERNAL_INIT(&intern);
|
||||
|
||||
if (rt_db_get_internal(&intern, dp, dbip, NULL) < 0)
|
||||
return false;
|
||||
|
||||
if (intern.idb_type != ID_BREP) {
|
||||
rt_db_free_internal(&intern);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct rt_brep_internal *bi = (struct rt_brep_internal *)intern.idb_ptr;
|
||||
if (!bi || !bi->brep) {
|
||||
rt_db_free_internal(&intern);
|
||||
return false;
|
||||
}
|
||||
|
||||
ON_3dmObjectAttributes attrs;
|
||||
attrs.Default();
|
||||
attrs.m_name = clean_name(dp->d_namep, "brlcad_brep", index).c_str();
|
||||
model.AddModelGeometryComponent(ON_Brep::New(*bi->brep), &attrs, true);
|
||||
|
||||
rt_db_free_internal(&intern);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
free_child_matrices(matp_t *mats)
|
||||
{
|
||||
if (!mats)
|
||||
return;
|
||||
|
||||
for (int i = 0; mats[i]; ++i)
|
||||
bu_free(mats[i], "free matrix");
|
||||
bu_free(mats, "free mats array");
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
append_breps_from_dp(struct db_i *dbip, const struct directory *dp, ONX_Model &model, int &index, std::set<const struct directory *> &seen_breps, std::set<const struct directory *> &seen_combs)
|
||||
{
|
||||
if (!dbip || !dp)
|
||||
return 0;
|
||||
|
||||
if (dp->d_flags & RT_DIR_SOLID) {
|
||||
if (seen_breps.count(dp))
|
||||
return 0;
|
||||
seen_breps.insert(dp);
|
||||
if (append_brep(dbip, dp, model, index)) {
|
||||
++index;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(dp->d_flags & RT_DIR_COMB) || seen_combs.count(dp))
|
||||
return 0;
|
||||
seen_combs.insert(dp);
|
||||
|
||||
struct rt_db_internal intern;
|
||||
RT_DB_INTERNAL_INIT(&intern);
|
||||
|
||||
if (rt_db_get_internal(&intern, dp, dbip, NULL) < 0)
|
||||
return 0;
|
||||
|
||||
if (intern.idb_type != ID_COMBINATION) {
|
||||
rt_db_free_internal(&intern);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct rt_comb_internal *comb = (struct rt_comb_internal *)intern.idb_ptr;
|
||||
struct directory **children = NULL;
|
||||
matp_t *mats = NULL;
|
||||
int child_count = db_comb_children(dbip, comb, &children, NULL, &mats);
|
||||
int brep_count = 0;
|
||||
|
||||
for (int i = 0; i < child_count && children && children[i] != RT_DIR_NULL; ++i)
|
||||
brep_count += append_breps_from_dp(dbip, children[i], model, index, seen_breps, seen_combs);
|
||||
|
||||
if (children)
|
||||
bu_free(children, "free children struct directory ptr array");
|
||||
free_child_matrices(mats);
|
||||
rt_db_free_internal(&intern);
|
||||
|
||||
return brep_count;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
append_selected_breps(struct db_i *dbip, const gcv_opts *gcv_options, ONX_Model &model)
|
||||
{
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
std::set<const struct directory *> seen_breps;
|
||||
std::set<const struct directory *> seen_combs;
|
||||
|
||||
if (gcv_options && gcv_options->num_objects) {
|
||||
for (size_t i = 0; i < gcv_options->num_objects; ++i) {
|
||||
const char *name = gcv_options->object_names[i];
|
||||
const struct directory *dp = db_lookup(dbip, name, LOOKUP_QUIET);
|
||||
if (!dp)
|
||||
continue;
|
||||
count += append_breps_from_dp(dbip, dp, model, index, seen_breps, seen_combs);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
struct directory *dp;
|
||||
FOR_ALL_DIRECTORY_START(dp, dbip) {
|
||||
if (dp->d_flags & RT_DIR_SOLID) {
|
||||
if (!seen_breps.count(dp) && append_brep(dbip, dp, model, index)) {
|
||||
seen_breps.insert(dp);
|
||||
++count;
|
||||
++index;
|
||||
}
|
||||
}
|
||||
} FOR_ALL_DIRECTORY_END;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
freecad_write(gcv_context *context, const gcv_opts *gcv_options, const void *UNUSED(options_data), const char *dest_path)
|
||||
{
|
||||
if (!context || !context->dbip || !dest_path)
|
||||
return 0;
|
||||
|
||||
ONX_Model model;
|
||||
model.AddDefaultLayer(L"Default", ON_Color::UnsetColor);
|
||||
model.m_properties.m_RevisionHistory.NewRevision();
|
||||
model.m_properties.m_Application.m_application_name = "BRL-CAD gcv FreeCAD writer";
|
||||
|
||||
int brep_count = append_selected_breps(context->dbip, gcv_options, model);
|
||||
if (brep_count <= 0) {
|
||||
bu_log("freecad_write: no BRep primitives available to export\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int written = open2open::ONX_ModelToFCStdFile(dest_path, model);
|
||||
if (written <= 0) {
|
||||
bu_log("freecad_write: failed to write %s\n", dest_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (written < brep_count)
|
||||
bu_log("freecad_write: wrote %d of %d BRep primitives to %s\n", written, brep_count, dest_path);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
extern "C" const struct gcv_filter gcv_conv_freecad_write = {
|
||||
"FreeCAD Writer", GCV_FILTER_WRITE, BU_MIME_MODEL_VND_FREECAD, NULL,
|
||||
NULL, NULL, freecad_write
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Local Variables:
|
||||
// tab-width: 8
|
||||
// mode: C++
|
||||
// c-basic-offset: 4
|
||||
// indent-tabs-mode: t
|
||||
// c-file-style: "stroustrup"
|
||||
// End:
|
||||
// ex: shiftwidth=4 tabstop=8
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2026, open2open contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// brep_convert.h — B-Rep topology conversion helpers.
|
||||
//
|
||||
// These functions translate complete B-Rep solid models between openNURBS
|
||||
// (ON_Brep) and OpenCASCADE (TopoDS_Shape).
|
||||
|
||||
#ifndef OPEN2OPEN_BREP_CONVERT_H
|
||||
#define OPEN2OPEN_BREP_CONVERT_H
|
||||
|
||||
#include <TopoDS_Shape.hxx>
|
||||
|
||||
// Forward declarations from openNURBS
|
||||
class ON_Brep;
|
||||
|
||||
namespace open2open {
|
||||
|
||||
/// Convert an openNURBS B-Rep to an OCCT TopoDS_Shape (Shell or Solid).
|
||||
///
|
||||
/// All NURBS curves and surfaces in the brep are translated using the
|
||||
/// geometry helpers from geom_convert.h. Non-NURBS primitives are
|
||||
/// first converted to their NURBS representations via
|
||||
/// ON_Curve::NurbsCurve() / ON_Surface::NurbsSurface().
|
||||
///
|
||||
/// @param brep Source B-Rep solid.
|
||||
/// @param linear_tolerance Positional tolerance propagated to OCCT topology.
|
||||
/// @return A TopoDS_Solid (or TopoDS_Shell for open models).
|
||||
/// A null shape is returned on failure.
|
||||
TopoDS_Shape ON_BrepToOCCT(const ON_Brep& brep,
|
||||
double linear_tolerance = 1e-6);
|
||||
|
||||
/// Convert an OCCT TopoDS_Shape to an openNURBS ON_Brep.
|
||||
///
|
||||
/// The function traverses the shape with TopExp_Explorer, translates each
|
||||
/// geometric entity to NURBS using the geometry helpers, and populates the
|
||||
/// provided ON_Brep with the resulting topology.
|
||||
///
|
||||
/// @param shape Source OCCT shape (must contain Face topology).
|
||||
/// @param brep Destination B-Rep (will be cleared first).
|
||||
/// @param linear_tolerance Tolerance used when building the ON_Brep.
|
||||
/// @return true on success; false if the conversion failed.
|
||||
bool OCCTToON_Brep(const TopoDS_Shape& shape,
|
||||
ON_Brep& brep,
|
||||
double linear_tolerance = 1e-6);
|
||||
|
||||
} // namespace open2open
|
||||
|
||||
#endif // OPEN2OPEN_BREP_CONVERT_H
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2026, open2open contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// fcstd_convert.h — FreeCAD .FCStd archive reader.
|
||||
//
|
||||
// Provides read-only access to the metadata and display colours stored in a
|
||||
// FreeCAD `.FCStd` archive, complementing the B-Rep conversion functions in
|
||||
// `brep_convert.h`.
|
||||
//
|
||||
// A `.FCStd` file is a standard ZIP archive containing:
|
||||
//
|
||||
// Document.xml — object tree (type, name, label, shape file mapping)
|
||||
// GuiDocument.xml — per-object display settings (colour, visibility …)
|
||||
// PartShape.brp, — OpenCASCADE B-Rep shapes in OCCT BRep text format
|
||||
// PartShape1.brp …
|
||||
// DiffuseColor, — per-face colour lists (binary, one per shape)
|
||||
// DiffuseColor1 …
|
||||
//
|
||||
// DiffuseColor binary format
|
||||
// --------------------------
|
||||
// Each `DiffuseColor` file contains:
|
||||
// uint32 count (little-endian: number of faces)
|
||||
// uint32 color[count] (per face, packed as 0xRRGGBBAA little-endian)
|
||||
//
|
||||
// Note: FreeCAD stores alpha = 0 for fully opaque faces (inverted convention).
|
||||
//
|
||||
// Usage
|
||||
// -----
|
||||
// open2open::FcstdDoc doc;
|
||||
// if (open2open::ReadFcstdDoc("/path/to/Model.FCStd", doc)) {
|
||||
// for (auto& obj : doc.objects) {
|
||||
// std::printf("name=%s brp=%s faces=%zu\n",
|
||||
// obj.label.c_str(), obj.brp_file.c_str(),
|
||||
// obj.face_colors.size());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Dependencies
|
||||
// ------------
|
||||
// In this BRL-CAD-local copy, FCStd archive I/O is backed by zlib through the
|
||||
// plugin's narrow archive helper. OPEN2OPEN_HAVE_LIBZIP is kept as the
|
||||
// existing open2open compile guard for the FCStd read/write entry points.
|
||||
|
||||
#ifndef OPEN2OPEN_FCSTD_CONVERT_H
|
||||
#define OPEN2OPEN_FCSTD_CONVERT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace open2open {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Per-object display colour (packed 0xRRGGBBAA, alpha=0 → fully opaque)
|
||||
// ---------------------------------------------------------------------------
|
||||
using FcstdColor = std::uint32_t;
|
||||
|
||||
// Convenience helpers for unpacking a FcstdColor.
|
||||
inline std::uint8_t FcstdColorR(FcstdColor c) { return (c >> 24) & 0xFFu; }
|
||||
inline std::uint8_t FcstdColorG(FcstdColor c) { return (c >> 16) & 0xFFu; }
|
||||
inline std::uint8_t FcstdColorB(FcstdColor c) { return (c >> 8) & 0xFFu; }
|
||||
// alpha=0 means fully opaque in FreeCAD's convention.
|
||||
inline std::uint8_t FcstdColorA(FcstdColor c) { return (c ) & 0xFFu; }
|
||||
|
||||
// Convert FreeCAD packed color to a normalised [0,1] RGBA tuple.
|
||||
// FreeCAD alpha=0 → standard alpha=1 (opaque).
|
||||
inline void FcstdColorToRGBAf(FcstdColor c,
|
||||
float& r, float& g, float& b, float& a)
|
||||
{
|
||||
r = FcstdColorR(c) / 255.0f;
|
||||
g = FcstdColorG(c) / 255.0f;
|
||||
b = FcstdColorB(c) / 255.0f;
|
||||
std::uint8_t fc_alpha = FcstdColorA(c);
|
||||
a = (fc_alpha == 0) ? 1.0f : (1.0f - fc_alpha / 255.0f);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Parse a DiffuseColor binary blob (in memory) into per-face colors.
|
||||
//
|
||||
// @param data Pointer to the raw bytes (e.g. content of DiffuseColor).
|
||||
// @param size Number of bytes.
|
||||
// @param colors_out Receives count packed FcstdColor values; cleared first.
|
||||
// @return true on success; false if the data is truncated/invalid.
|
||||
// ---------------------------------------------------------------------------
|
||||
bool ParseDiffuseColors(const void* data,
|
||||
std::size_t size,
|
||||
std::vector<FcstdColor>& colors_out);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Information about one geometric object inside an FCStd archive.
|
||||
// ---------------------------------------------------------------------------
|
||||
struct FcstdObject {
|
||||
std::string name; ///< Internal name used as key (e.g. "Part__Feature")
|
||||
std::string type; ///< FreeCAD object type (e.g. "Part::Feature")
|
||||
std::string label; ///< User-facing label (e.g. "ESQ-126-38-G-D_socket")
|
||||
std::string part_label; ///< Label of the containing App::Part (empty if none)
|
||||
std::string brp_file; ///< Path inside ZIP to the BRep file (e.g. "PartShape.brp")
|
||||
FcstdColor shape_color = 0xC8C8C800u; ///< Overall shape colour (packed RGBA, FreeCAD alpha=0 → opaque)
|
||||
bool visible = true;
|
||||
std::vector<FcstdColor> face_colors; ///< Per-face colours (may be empty)
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Document-level metadata extracted from Document.xml.
|
||||
// ---------------------------------------------------------------------------
|
||||
struct FcstdDocMeta {
|
||||
std::string label; ///< Document label (e.g. "ESQ-126-38-G-D")
|
||||
std::string created_by;
|
||||
std::string creation_date;
|
||||
std::string last_modified_date;
|
||||
std::string comment;
|
||||
std::string company;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Everything read from one FCStd archive.
|
||||
// ---------------------------------------------------------------------------
|
||||
struct FcstdDoc {
|
||||
FcstdDocMeta meta;
|
||||
std::vector<FcstdObject> objects; ///< Only objects with a BRep shape file
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Read an FCStd archive and populate a FcstdDoc.
|
||||
//
|
||||
// Reads Document.xml, GuiDocument.xml, and all DiffuseColor binary blobs.
|
||||
// Objects that have no associated BRep file (e.g. App::Origin, App::Line) are
|
||||
// silently skipped.
|
||||
//
|
||||
// @param path Filesystem path to the .FCStd file.
|
||||
// @param doc Output: populated on success; unchanged on failure.
|
||||
// @return true if the archive was opened and at least Document.xml was
|
||||
// parsed; false if the file could not be opened as a ZIP archive.
|
||||
//
|
||||
// This function requires OPEN2OPEN_HAVE_LIBZIP, which enables the local
|
||||
// zlib-backed FCStd archive implementation in the BRL-CAD plugin build.
|
||||
// ---------------------------------------------------------------------------
|
||||
bool ReadFcstdDoc(const std::string& path, FcstdDoc& doc);
|
||||
|
||||
#ifdef OPEN2OPEN_HAVE_LIBZIP
|
||||
// Forward-declare the openNURBS model type in its actual namespace.
|
||||
// Callers must include "opennurbs.h" themselves.
|
||||
} // namespace open2open
|
||||
class ONX_Model;
|
||||
namespace open2open {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Convert an FCStd file to an ONX_Model.
|
||||
//
|
||||
// Opens the archive at @p path, reads all B-Rep shapes (*.brp entries),
|
||||
// converts each shape via OCCTToON_Brep(), and appends to @p model.
|
||||
// Object name, display colour and layer name are propagated as model
|
||||
// attributes. Per-face colours from DiffuseColor are stored on the ON_Brep
|
||||
// using the first face colour as the object colour.
|
||||
//
|
||||
// @param path Filesystem path to the .FCStd file.
|
||||
// @param model Destination model. Existing content is NOT cleared.
|
||||
// @param tol Linear tolerance passed to OCCTToON_Brep().
|
||||
// @return Number of shapes successfully converted (0 on total failure).
|
||||
// ---------------------------------------------------------------------------
|
||||
int FCStdFileToONX_Model(const std::string& path,
|
||||
::ONX_Model& model,
|
||||
double tol = 1e-6);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Convert an ONX_Model to a FreeCAD .FCStd archive.
|
||||
//
|
||||
// Writes each B-Rep geometry object in @p model as an OCCT BRep text file
|
||||
// inside the ZIP archive, along with a minimal Document.xml that links
|
||||
// object names to their shape files, and a GuiDocument.xml with display
|
||||
// colours.
|
||||
//
|
||||
// @param path Output filesystem path for the .FCStd file.
|
||||
// @param model Source openNURBS model.
|
||||
// @param tol Linear tolerance passed to ON_BrepToOCCT().
|
||||
// @return Number of shapes successfully written (0 on total failure).
|
||||
// ---------------------------------------------------------------------------
|
||||
int ONX_ModelToFCStdFile(const std::string& path,
|
||||
const ::ONX_Model& model,
|
||||
double tol = 1e-6);
|
||||
#endif // OPEN2OPEN_HAVE_LIBZIP
|
||||
|
||||
} // namespace open2open
|
||||
|
||||
#endif // OPEN2OPEN_FCSTD_CONVERT_H
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2026, open2open contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// geom_convert.h — Low-level NURBS geometry conversion helpers.
|
||||
//
|
||||
// These functions translate individual curve and surface objects between
|
||||
// openNURBS and OpenCASCADE (OCCT) representations. They are the building
|
||||
// blocks for the higher-level B-Rep topology conversion in brep_convert.h.
|
||||
|
||||
#ifndef OPEN2OPEN_GEOM_CONVERT_H
|
||||
#define OPEN2OPEN_GEOM_CONVERT_H
|
||||
|
||||
#include <Standard_Handle.hxx>
|
||||
#include <Geom_BSplineCurve.hxx>
|
||||
#include <Geom_BSplineSurface.hxx>
|
||||
#include <Geom2d_BSplineCurve.hxx>
|
||||
|
||||
// Forward declarations from openNURBS
|
||||
class ON_NurbsCurve;
|
||||
class ON_NurbsSurface;
|
||||
|
||||
namespace open2open {
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// openNURBS → OCCT
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/// Convert a 3-D openNURBS NURBS curve to an OCCT Geom_BSplineCurve.
|
||||
/// @param c Source curve (m_dim must be 3).
|
||||
/// @return Null handle if the conversion fails (e.g. wrong dimension).
|
||||
Handle(Geom_BSplineCurve) ON_NurbsCurveToOCCT(const ON_NurbsCurve& c);
|
||||
|
||||
/// Convert a 2-D openNURBS NURBS curve (parameter-space trim) to a
|
||||
/// Geom2d_BSplineCurve.
|
||||
/// @param c Source curve (m_dim must be 2).
|
||||
/// @return Null handle on failure.
|
||||
Handle(Geom2d_BSplineCurve) ON_NurbsCurve2dToOCCT(const ON_NurbsCurve& c);
|
||||
|
||||
/// Convert an openNURBS NURBS surface to an OCCT Geom_BSplineSurface.
|
||||
/// @param s Source surface.
|
||||
/// @return Null handle on failure.
|
||||
Handle(Geom_BSplineSurface) ON_NurbsSurfaceToOCCT(const ON_NurbsSurface& s);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// OCCT → openNURBS
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/// Convert an OCCT Geom_BSplineCurve to an openNURBS ON_NurbsCurve.
|
||||
/// @param c Source curve handle.
|
||||
/// @param out Destination object (will be overwritten).
|
||||
/// @return true on success.
|
||||
bool OCCTCurveToON(const Handle(Geom_BSplineCurve)& c, ON_NurbsCurve& out);
|
||||
|
||||
/// Convert an OCCT Geom_BSplineSurface to an openNURBS ON_NurbsSurface.
|
||||
/// @param s Source surface handle.
|
||||
/// @param out Destination object (will be overwritten).
|
||||
/// @return true on success.
|
||||
bool OCCTSurfaceToON(const Handle(Geom_BSplineSurface)& s, ON_NurbsSurface& out);
|
||||
|
||||
} // namespace open2open
|
||||
|
||||
#endif // OPEN2OPEN_GEOM_CONVERT_H
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2026, open2open contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// mesh_convert.h — Polygonal mesh conversion helpers.
|
||||
//
|
||||
// These functions translate polygonal mesh geometry between openNURBS
|
||||
// (ON_Mesh) and OpenCASCADE (Poly_Triangulation).
|
||||
|
||||
#ifndef OPEN2OPEN_MESH_CONVERT_H
|
||||
#define OPEN2OPEN_MESH_CONVERT_H
|
||||
|
||||
#include <Standard_Handle.hxx>
|
||||
#include <Poly_Triangulation.hxx>
|
||||
|
||||
// Forward declaration from openNURBS
|
||||
class ON_Mesh;
|
||||
|
||||
namespace open2open {
|
||||
|
||||
/// Convert an openNURBS polygon mesh to an OCCT Poly_Triangulation.
|
||||
///
|
||||
/// Quad faces in the openNURBS mesh are triangulated by splitting along
|
||||
/// the diagonal between vertex 0 and vertex 2 of each quad. Vertex
|
||||
/// positions, per-vertex normals, and per-vertex texture coordinates
|
||||
/// are all preserved when present. Vertex colors are not carried over
|
||||
/// because Poly_Triangulation does not support per-vertex colour data.
|
||||
///
|
||||
/// Vertex positions are taken from the double-precision array (m_dV) when
|
||||
/// it is populated; otherwise from the single-precision array (m_V).
|
||||
///
|
||||
/// @param mesh Source openNURBS mesh.
|
||||
/// @return A new Poly_Triangulation handle, or a null handle if the
|
||||
/// mesh is empty or the conversion fails.
|
||||
Handle(Poly_Triangulation) ON_MeshToOCCT(const ON_Mesh& mesh);
|
||||
|
||||
/// Convert an OCCT Poly_Triangulation to an openNURBS ON_Mesh.
|
||||
///
|
||||
/// Each triangle becomes an ON_MeshFace using the degenerate-quad encoding
|
||||
/// that openNURBS uses for triangles: vi[2] == vi[3]. Per-vertex normals
|
||||
/// and UV texture coordinates are copied when the triangulation provides them.
|
||||
///
|
||||
/// @param tri Source OCCT triangulation (must not be null).
|
||||
/// @param mesh Destination mesh (cleared before writing).
|
||||
/// @return true on success; false if @p tri is null or empty.
|
||||
bool OCCTToON_Mesh(const Handle(Poly_Triangulation)& tri, ON_Mesh& mesh);
|
||||
|
||||
} // namespace open2open
|
||||
|
||||
#endif // OPEN2OPEN_MESH_CONVERT_H
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,413 @@
|
||||
// Copyright (c) 2026, open2open contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// geom_convert.cpp — NURBS curve and surface translation between
|
||||
// openNURBS and OpenCASCADE (OCCT).
|
||||
|
||||
#include "open2open/geom_convert.h"
|
||||
|
||||
// ---- openNURBS ----
|
||||
#include "opennurbs.h"
|
||||
|
||||
// ---- OCCT ----
|
||||
#include <TColgp_Array1OfPnt.hxx>
|
||||
#include <TColgp_Array1OfPnt2d.hxx>
|
||||
#include <TColgp_Array2OfPnt.hxx>
|
||||
#include <TColStd_Array1OfReal.hxx>
|
||||
#include <TColStd_Array1OfInteger.hxx>
|
||||
#include <TColStd_Array2OfReal.hxx>
|
||||
#include <Geom_BSplineCurve.hxx>
|
||||
#include <Geom_BSplineSurface.hxx>
|
||||
#include <Geom2d_BSplineCurve.hxx>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace open2open {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Convert an openNURBS flat knot array to OCCT (unique knots + multiplicities).
|
||||
///
|
||||
/// openNURBS stores `order + cv_count - 2` knot values – omitting one
|
||||
/// "phantom" knot at each end that OCCT needs. This function builds a
|
||||
/// temporary extended array of length `num_knots + 2`, prepending
|
||||
/// @p phantom_lo and appending @p phantom_hi, then deduplicates into
|
||||
/// the OCCT (knot, multiplicity) format.
|
||||
///
|
||||
/// For clamped (non-periodic) curves pass `phantom_lo = flat_knots[0]`
|
||||
/// and `phantom_hi = flat_knots[num_knots-1]`.
|
||||
///
|
||||
/// For uniform (non-clamped, non-periodic) curves pass extrapolated values:
|
||||
/// `phantom_lo = 2*k[0] - k[1]` and `phantom_hi = 2*k[last] - k[last-1]`.
|
||||
///
|
||||
/// For periodic curves pass `periodic = true` and the phantoms are ignored.
|
||||
static void ExtractKnotsAndMults(const double* flat_knots,
|
||||
int num_knots,
|
||||
TColStd_Array1OfReal& knots,
|
||||
TColStd_Array1OfInteger& mults,
|
||||
double phantom_lo,
|
||||
double phantom_hi,
|
||||
bool periodic = false)
|
||||
{
|
||||
if (periodic) {
|
||||
// Periodic: pass through without adding phantom knots
|
||||
int num_distinct = 0;
|
||||
for (int i = 0; i < num_knots; ) {
|
||||
++num_distinct;
|
||||
int j = i + 1;
|
||||
while (j < num_knots && flat_knots[j] == flat_knots[i]) ++j;
|
||||
i = j;
|
||||
}
|
||||
knots.Resize(1, num_distinct, Standard_False);
|
||||
mults.Resize(1, num_distinct, Standard_False);
|
||||
int idx = 1, i = 0;
|
||||
while (i < num_knots) {
|
||||
int j = i + 1;
|
||||
while (j < num_knots && flat_knots[j] == flat_knots[i]) ++j;
|
||||
knots.SetValue(idx, flat_knots[i]);
|
||||
mults.SetValue(idx, j - i);
|
||||
++idx; i = j;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Build extended array: [phantom_lo] + flat_knots[] + [phantom_hi]
|
||||
const int ext_len = num_knots + 2;
|
||||
std::vector<double> ext(ext_len);
|
||||
ext[0] = phantom_lo;
|
||||
for (int k = 0; k < num_knots; ++k)
|
||||
ext[k + 1] = flat_knots[k];
|
||||
ext[ext_len - 1] = phantom_hi;
|
||||
|
||||
// Count distinct values in the extended array
|
||||
int num_distinct = 0;
|
||||
for (int i = 0; i < ext_len; ) {
|
||||
++num_distinct;
|
||||
int j = i + 1;
|
||||
while (j < ext_len && ext[j] == ext[i]) ++j;
|
||||
i = j;
|
||||
}
|
||||
|
||||
knots.Resize(1, num_distinct, Standard_False);
|
||||
mults.Resize(1, num_distinct, Standard_False);
|
||||
|
||||
int idx = 1, i = 0;
|
||||
while (i < ext_len) {
|
||||
int j = i + 1;
|
||||
while (j < ext_len && ext[j] == ext[i]) ++j;
|
||||
knots.SetValue(idx, ext[i]);
|
||||
mults.SetValue(idx, j - i);
|
||||
++idx; i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ON_NurbsCurve → Geom_BSplineCurve (3-D)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Handle(Geom_BSplineCurve) ON_NurbsCurveToOCCT(const ON_NurbsCurve& c)
|
||||
{
|
||||
if (c.m_dim != 3 || c.m_order < 2 || c.m_cv_count < c.m_order ||
|
||||
!c.m_cv || !c.m_knot)
|
||||
return Handle(Geom_BSplineCurve)();
|
||||
|
||||
const int degree = c.m_order - 1;
|
||||
const int num_poles = c.m_cv_count;
|
||||
const bool rational = (c.m_is_rat != 0);
|
||||
// ON_IsKnotVectorPeriodic detects uniform knot spacing at both ends.
|
||||
// We always build a non-periodic OCCT B-spline (the evaluation is
|
||||
// identical) and add the two phantom knots that openNURBS omits.
|
||||
// True closed curves are handled as non-periodic with a domain
|
||||
// restricted to the interior knot span.
|
||||
|
||||
// --- control points (OCCT is 1-indexed, Euclidean) ---
|
||||
TColgp_Array1OfPnt poles(1, num_poles);
|
||||
TColStd_Array1OfReal weights(1, num_poles);
|
||||
|
||||
for (int i = 0; i < num_poles; ++i) {
|
||||
const double* cv = c.m_cv + static_cast<std::ptrdiff_t>(i) * c.m_cv_stride;
|
||||
if (rational) {
|
||||
double w = cv[c.m_dim]; // homogeneous weight
|
||||
if (w == 0.0) return Handle(Geom_BSplineCurve)();
|
||||
poles.SetValue(i + 1, gp_Pnt(cv[0] / w, cv[1] / w, cv[2] / w));
|
||||
weights.SetValue(i + 1, w);
|
||||
} else {
|
||||
poles.SetValue(i + 1, gp_Pnt(cv[0], cv[1], cv[2]));
|
||||
weights.SetValue(i + 1, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// --- knot vector ---
|
||||
// openNURBS omits one "phantom" knot at each end; OCCT requires them.
|
||||
// For clamped ends the phantom = boundary knot value.
|
||||
// For non-clamped ends extrapolate by one knot-step.
|
||||
const int num_flat_knots = c.m_order + c.m_cv_count - 2;
|
||||
const double k0 = c.m_knot[0];
|
||||
const double k1 = c.m_knot[1];
|
||||
const double kn1 = c.m_knot[num_flat_knots - 2];
|
||||
const double kn = c.m_knot[num_flat_knots - 1];
|
||||
const double phantom_lo = (k0 != k1) ? 2.0 * k0 - k1 : k0;
|
||||
const double phantom_hi = (kn != kn1) ? 2.0 * kn - kn1 : kn;
|
||||
|
||||
TColStd_Array1OfReal knots(1, 1);
|
||||
TColStd_Array1OfInteger mults(1, 1);
|
||||
ExtractKnotsAndMults(c.m_knot, num_flat_knots, knots, mults,
|
||||
phantom_lo, phantom_hi, false);
|
||||
|
||||
try {
|
||||
if (rational) {
|
||||
return new Geom_BSplineCurve(poles, weights, knots, mults,
|
||||
degree, false);
|
||||
} else {
|
||||
return new Geom_BSplineCurve(poles, knots, mults,
|
||||
degree, false);
|
||||
}
|
||||
} catch (...) {
|
||||
return Handle(Geom_BSplineCurve)();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ON_NurbsCurve → Geom2d_BSplineCurve (2-D parameter-space)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Handle(Geom2d_BSplineCurve) ON_NurbsCurve2dToOCCT(const ON_NurbsCurve& c)
|
||||
{
|
||||
if (c.m_dim != 2 || c.m_order < 2 || c.m_cv_count < c.m_order ||
|
||||
!c.m_cv || !c.m_knot)
|
||||
return Handle(Geom2d_BSplineCurve)();
|
||||
|
||||
const int degree = c.m_order - 1;
|
||||
const int num_poles = c.m_cv_count;
|
||||
const bool rational = (c.m_is_rat != 0);
|
||||
|
||||
TColgp_Array1OfPnt2d poles(1, num_poles);
|
||||
TColStd_Array1OfReal weights(1, num_poles);
|
||||
|
||||
for (int i = 0; i < num_poles; ++i) {
|
||||
const double* cv = c.m_cv + static_cast<std::ptrdiff_t>(i) * c.m_cv_stride;
|
||||
if (rational) {
|
||||
double w = cv[c.m_dim];
|
||||
if (w == 0.0) return Handle(Geom2d_BSplineCurve)();
|
||||
poles.SetValue(i + 1, gp_Pnt2d(cv[0] / w, cv[1] / w));
|
||||
weights.SetValue(i + 1, w);
|
||||
} else {
|
||||
poles.SetValue(i + 1, gp_Pnt2d(cv[0], cv[1]));
|
||||
weights.SetValue(i + 1, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
const int num_flat_knots = c.m_order + c.m_cv_count - 2;
|
||||
const double k2_0 = c.m_knot[0];
|
||||
const double k2_1 = c.m_knot[1];
|
||||
const double k2_n1 = c.m_knot[num_flat_knots - 2];
|
||||
const double k2_n = c.m_knot[num_flat_knots - 1];
|
||||
const double plo2 = (k2_0 != k2_1) ? 2.0 * k2_0 - k2_1 : k2_0;
|
||||
const double phi2 = (k2_n != k2_n1) ? 2.0 * k2_n - k2_n1 : k2_n;
|
||||
|
||||
TColStd_Array1OfReal knots(1, 1);
|
||||
TColStd_Array1OfInteger mults(1, 1);
|
||||
ExtractKnotsAndMults(c.m_knot, num_flat_knots, knots, mults,
|
||||
plo2, phi2, false);
|
||||
|
||||
try {
|
||||
if (rational) {
|
||||
return new Geom2d_BSplineCurve(poles, weights, knots, mults,
|
||||
degree, false);
|
||||
} else {
|
||||
return new Geom2d_BSplineCurve(poles, knots, mults,
|
||||
degree, false);
|
||||
}
|
||||
} catch (...) {
|
||||
return Handle(Geom2d_BSplineCurve)();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ON_NurbsSurface → Geom_BSplineSurface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Handle(Geom_BSplineSurface) ON_NurbsSurfaceToOCCT(const ON_NurbsSurface& s)
|
||||
{
|
||||
if (s.m_dim != 3 || s.m_order[0] < 2 || s.m_order[1] < 2 ||
|
||||
s.m_cv_count[0] < s.m_order[0] || s.m_cv_count[1] < s.m_order[1] ||
|
||||
!s.m_cv || !s.m_knot[0] || !s.m_knot[1])
|
||||
return Handle(Geom_BSplineSurface)();
|
||||
|
||||
const int udeg = s.m_order[0] - 1;
|
||||
const int vdeg = s.m_order[1] - 1;
|
||||
const int nu = s.m_cv_count[0];
|
||||
const int nv = s.m_cv_count[1];
|
||||
const bool rational = (s.m_is_rat != 0);
|
||||
|
||||
// OCCT Array2: rows = U index (1..nu), cols = V index (1..nv)
|
||||
TColgp_Array2OfPnt poles(1, nu, 1, nv);
|
||||
TColStd_Array2OfReal weights(1, nu, 1, nv);
|
||||
|
||||
for (int i = 0; i < nu; ++i) {
|
||||
for (int j = 0; j < nv; ++j) {
|
||||
const double* cv = s.m_cv
|
||||
+ static_cast<std::ptrdiff_t>(i) * s.m_cv_stride[0]
|
||||
+ static_cast<std::ptrdiff_t>(j) * s.m_cv_stride[1];
|
||||
if (rational) {
|
||||
double w = cv[s.m_dim];
|
||||
if (w == 0.0) return Handle(Geom_BSplineSurface)();
|
||||
poles.SetValue(i + 1, j + 1,
|
||||
gp_Pnt(cv[0] / w, cv[1] / w, cv[2] / w));
|
||||
weights.SetValue(i + 1, j + 1, w);
|
||||
} else {
|
||||
poles.SetValue(i + 1, j + 1,
|
||||
gp_Pnt(cv[0], cv[1], cv[2]));
|
||||
weights.SetValue(i + 1, j + 1, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int nu_knots = s.m_order[0] + s.m_cv_count[0] - 2;
|
||||
const int nv_knots = s.m_order[1] + s.m_cv_count[1] - 2;
|
||||
|
||||
// Compute phantom knot values for each parametric direction
|
||||
auto PhantomLo = [](const double* k, int n) -> double {
|
||||
return (k[0] != k[1]) ? 2.0 * k[0] - k[1] : k[0];
|
||||
};
|
||||
auto PhantomHi = [](const double* k, int n) -> double {
|
||||
return (k[n-1] != k[n-2]) ? 2.0 * k[n-1] - k[n-2] : k[n-1];
|
||||
};
|
||||
|
||||
TColStd_Array1OfReal uknots(1, 1), vknots(1, 1);
|
||||
TColStd_Array1OfInteger umults(1, 1), vmults(1, 1);
|
||||
ExtractKnotsAndMults(s.m_knot[0], nu_knots, uknots, umults,
|
||||
PhantomLo(s.m_knot[0], nu_knots),
|
||||
PhantomHi(s.m_knot[0], nu_knots), false);
|
||||
ExtractKnotsAndMults(s.m_knot[1], nv_knots, vknots, vmults,
|
||||
PhantomLo(s.m_knot[1], nv_knots),
|
||||
PhantomHi(s.m_knot[1], nv_knots), false);
|
||||
|
||||
try {
|
||||
if (rational) {
|
||||
return new Geom_BSplineSurface(poles, weights,
|
||||
uknots, vknots,
|
||||
umults, vmults,
|
||||
udeg, vdeg,
|
||||
false, false);
|
||||
} else {
|
||||
return new Geom_BSplineSurface(poles,
|
||||
uknots, vknots,
|
||||
umults, vmults,
|
||||
udeg, vdeg,
|
||||
false, false);
|
||||
}
|
||||
} catch (...) {
|
||||
return Handle(Geom_BSplineSurface)();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Geom_BSplineCurve → ON_NurbsCurve (3-D)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool OCCTCurveToON(const Handle(Geom_BSplineCurve)& c, ON_NurbsCurve& out)
|
||||
{
|
||||
if (c.IsNull()) return false;
|
||||
|
||||
const int degree = c->Degree();
|
||||
const int num_poles = c->NbPoles();
|
||||
const bool rational = c->IsRational() ? true : false;
|
||||
|
||||
if (!out.Create(3, rational, degree + 1, num_poles))
|
||||
return false;
|
||||
|
||||
// --- control points ---
|
||||
for (int i = 1; i <= num_poles; ++i) {
|
||||
gp_Pnt p = c->Pole(i);
|
||||
double w = rational ? c->Weight(i) : 1.0;
|
||||
// openNURBS rational curves store homogeneous coordinates
|
||||
out.SetCV(i - 1,
|
||||
ON_4dPoint(p.X() * w, p.Y() * w, p.Z() * w, w));
|
||||
}
|
||||
|
||||
// --- knot vector ---
|
||||
// OCCT stores sum(mults) = num_poles + degree + 1 (non-periodic).
|
||||
// openNURBS stores sum(flat) = num_poles + degree - 1 (2 fewer).
|
||||
// The conversion always added one phantom knot at each end so we
|
||||
// always strip exactly one from the first and last multiplicity groups.
|
||||
const int num_knot_groups = c->NbKnots();
|
||||
int ki = 0;
|
||||
for (int j = 1; j <= num_knot_groups; ++j) {
|
||||
double val = c->Knot(j);
|
||||
int mult = c->Multiplicity(j);
|
||||
if (j == 1 || j == num_knot_groups)
|
||||
mult -= 1;
|
||||
for (int m = 0; m < mult; ++m) {
|
||||
out.m_knot[ki++] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return out.IsValid() ? true : false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Geom_BSplineSurface → ON_NurbsSurface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool OCCTSurfaceToON(const Handle(Geom_BSplineSurface)& s, ON_NurbsSurface& out)
|
||||
{
|
||||
if (s.IsNull()) return false;
|
||||
|
||||
const int udeg = s->UDegree();
|
||||
const int vdeg = s->VDegree();
|
||||
const int nu = s->NbUPoles();
|
||||
const int nv = s->NbVPoles();
|
||||
const bool rational = s->IsURational() || s->IsVRational();
|
||||
|
||||
if (!out.Create(3, rational, udeg + 1, vdeg + 1, nu, nv))
|
||||
return false;
|
||||
|
||||
// --- control points ---
|
||||
for (int i = 1; i <= nu; ++i) {
|
||||
for (int j = 1; j <= nv; ++j) {
|
||||
gp_Pnt p = s->Pole(i, j);
|
||||
double w = rational ? s->Weight(i, j) : 1.0;
|
||||
out.SetCV(i - 1, j - 1,
|
||||
ON_4dPoint(p.X() * w, p.Y() * w, p.Z() * w, w));
|
||||
}
|
||||
}
|
||||
|
||||
// --- U knots ---
|
||||
{
|
||||
int ki = 0;
|
||||
const int nku = s->NbUKnots();
|
||||
// OCCT stores sum(mults) = nu + udeg + 1 (non-periodic).
|
||||
// openNURBS stores sum(flat) = nu + udeg - 1 (2 fewer).
|
||||
// Whether clamped or non-clamped, the conversion always added one
|
||||
// phantom knot at each end, so we always strip one from each end.
|
||||
for (int j = 1; j <= nku; ++j) {
|
||||
double val = s->UKnot(j);
|
||||
int mult = s->UMultiplicity(j);
|
||||
if (j == 1 || j == nku)
|
||||
mult -= 1;
|
||||
for (int m = 0; m < mult; ++m)
|
||||
out.m_knot[0][ki++] = val;
|
||||
}
|
||||
}
|
||||
|
||||
// --- V knots ---
|
||||
{
|
||||
int ki = 0;
|
||||
const int nkv = s->NbVKnots();
|
||||
for (int j = 1; j <= nkv; ++j) {
|
||||
double val = s->VKnot(j);
|
||||
int mult = s->VMultiplicity(j);
|
||||
if (j == 1 || j == nkv)
|
||||
mult -= 1;
|
||||
for (int m = 0; m < mult; ++m)
|
||||
out.m_knot[1][ki++] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return out.IsValid() ? true : false;
|
||||
}
|
||||
|
||||
} // namespace open2open
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) 2026, open2open contributors
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// mesh_convert.cpp — Polygonal mesh translation between openNURBS and OCCT.
|
||||
|
||||
#include "open2open/mesh_convert.h"
|
||||
|
||||
// ---- openNURBS ----
|
||||
#include "opennurbs.h"
|
||||
|
||||
// ---- OCCT ----
|
||||
#include <Poly_Triangulation.hxx>
|
||||
#include <Poly_Triangle.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <gp_Vec3f.hxx>
|
||||
|
||||
namespace open2open {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ON_Mesh → Poly_Triangulation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Handle(Poly_Triangulation) ON_MeshToOCCT(const ON_Mesh& mesh)
|
||||
{
|
||||
const int nv = mesh.m_V.Count();
|
||||
const int nf = mesh.m_F.Count();
|
||||
if (nv <= 0 || nf <= 0)
|
||||
return Handle(Poly_Triangulation)();
|
||||
|
||||
// Count output triangles: quads become 2 triangles, tris stay as 1.
|
||||
int ntri = 0;
|
||||
for (int fi = 0; fi < nf; ++fi) {
|
||||
const ON_MeshFace& f = mesh.m_F[fi];
|
||||
ntri += f.IsTriangle() ? 1 : 2;
|
||||
}
|
||||
if (ntri == 0)
|
||||
return Handle(Poly_Triangulation)();
|
||||
|
||||
const bool has_uv = mesh.HasTextureCoordinates() &&
|
||||
mesh.m_T.Count() == nv;
|
||||
const bool has_normals = mesh.HasVertexNormals() &&
|
||||
mesh.m_N.Count() == nv;
|
||||
|
||||
Handle(Poly_Triangulation) tri =
|
||||
new Poly_Triangulation(nv, ntri, has_uv, has_normals);
|
||||
|
||||
// Copy vertices — prefer double-precision array when available.
|
||||
const bool use_double = (mesh.m_dV.Count() == nv);
|
||||
for (int vi = 0; vi < nv; ++vi) {
|
||||
gp_Pnt pt;
|
||||
if (use_double) {
|
||||
const ON_3dPoint& p = mesh.m_dV[vi];
|
||||
pt = gp_Pnt(p.x, p.y, p.z);
|
||||
} else {
|
||||
const ON_3fPoint& p = mesh.m_V[vi];
|
||||
pt = gp_Pnt(static_cast<double>(p.x),
|
||||
static_cast<double>(p.y),
|
||||
static_cast<double>(p.z));
|
||||
}
|
||||
tri->SetNode(vi + 1, pt); // Poly_Triangulation is 1-indexed
|
||||
}
|
||||
|
||||
// Copy per-vertex normals.
|
||||
if (has_normals) {
|
||||
for (int ni = 0; ni < nv; ++ni) {
|
||||
const ON_3fVector& n = mesh.m_N[ni];
|
||||
tri->SetNormal(ni + 1,
|
||||
gp_Vec3f(static_cast<float>(n.x),
|
||||
static_cast<float>(n.y),
|
||||
static_cast<float>(n.z)));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy per-vertex texture coordinates.
|
||||
if (has_uv) {
|
||||
for (int ui = 0; ui < nv; ++ui) {
|
||||
const ON_2fPoint& uv = mesh.m_T[ui];
|
||||
tri->SetUVNode(ui + 1,
|
||||
gp_Pnt2d(static_cast<double>(uv.x),
|
||||
static_cast<double>(uv.y)));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy faces — split quads into two triangles.
|
||||
// ON_MeshFace vi[] is 0-indexed; Poly_Triangle is 1-indexed.
|
||||
int tidx = 1;
|
||||
for (int fi = 0; fi < nf; ++fi) {
|
||||
const ON_MeshFace& f = mesh.m_F[fi];
|
||||
tri->SetTriangle(tidx++,
|
||||
Poly_Triangle(f.vi[0] + 1,
|
||||
f.vi[1] + 1,
|
||||
f.vi[2] + 1));
|
||||
if (!f.IsTriangle()) {
|
||||
// Second triangle covers the quad's second half:
|
||||
// vertices 0, 2, 3 of the original quad.
|
||||
tri->SetTriangle(tidx++,
|
||||
Poly_Triangle(f.vi[0] + 1,
|
||||
f.vi[2] + 1,
|
||||
f.vi[3] + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return tri;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Poly_Triangulation → ON_Mesh
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool OCCTToON_Mesh(const Handle(Poly_Triangulation)& tri, ON_Mesh& mesh)
|
||||
{
|
||||
if (tri.IsNull())
|
||||
return false;
|
||||
|
||||
const int nv = tri->NbNodes();
|
||||
const int nt = tri->NbTriangles();
|
||||
if (nv <= 0 || nt <= 0)
|
||||
return false;
|
||||
|
||||
mesh.Destroy();
|
||||
|
||||
// Allocate and fill vertex array.
|
||||
mesh.m_V.SetCapacity(nv);
|
||||
mesh.m_V.SetCount(nv);
|
||||
for (int vi = 1; vi <= nv; ++vi) {
|
||||
const gp_Pnt& p = tri->Node(vi);
|
||||
mesh.m_V[vi - 1] = ON_3fPoint(static_cast<float>(p.X()),
|
||||
static_cast<float>(p.Y()),
|
||||
static_cast<float>(p.Z()));
|
||||
}
|
||||
|
||||
// Allocate and fill per-vertex normal array.
|
||||
if (tri->HasNormals()) {
|
||||
mesh.m_N.SetCapacity(nv);
|
||||
mesh.m_N.SetCount(nv);
|
||||
for (int ni = 1; ni <= nv; ++ni) {
|
||||
gp_Vec3f n;
|
||||
tri->Normal(ni, n);
|
||||
mesh.m_N[ni - 1] = ON_3fVector(n.x(), n.y(), n.z());
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate and fill per-vertex UV array.
|
||||
if (tri->HasUVNodes()) {
|
||||
mesh.m_T.SetCapacity(nv);
|
||||
mesh.m_T.SetCount(nv);
|
||||
for (int ui = 1; ui <= nv; ++ui) {
|
||||
const gp_Pnt2d& uv = tri->UVNode(ui);
|
||||
mesh.m_T[ui - 1] = ON_2fPoint(static_cast<float>(uv.X()),
|
||||
static_cast<float>(uv.Y()));
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate and fill face array.
|
||||
// openNURBS encodes triangles as degenerate quads: vi[2] == vi[3].
|
||||
mesh.m_F.SetCapacity(nt);
|
||||
mesh.m_F.SetCount(nt);
|
||||
for (int ti = 1; ti <= nt; ++ti) {
|
||||
const Poly_Triangle& t = tri->Triangle(ti);
|
||||
ON_MeshFace& f = mesh.m_F[ti - 1];
|
||||
f.vi[0] = t.Value(1) - 1; // convert 1-indexed → 0-indexed
|
||||
f.vi[1] = t.Value(2) - 1;
|
||||
f.vi[2] = t.Value(3) - 1;
|
||||
f.vi[3] = f.vi[2]; // degenerate-quad triangle encoding
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace open2open
|
||||
Reference in New Issue
Block a user