Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ead9304
breaking change: don't trim loft at profiles
joergbrech Jun 11, 2026
f221bae
Merge branch 'main' into untrimmed-loft
joergbrech Jun 25, 2026
d3c4a95
some fixes
joergbrech Jun 25, 2026
a409ffc
Merge branch 'main' into untrimmed-loft
joergbrech Jun 25, 2026
de43b43
try to fix unit tests
joergbrech Jun 30, 2026
f5a5cea
Merge branch 'main' into untrimmed-loft
joergbrech Jun 30, 2026
0f212e1
revert whitespace changes
joergbrech Jun 30, 2026
08f2807
rever noop changes in CTiglAbstractGeometricComponent
joergbrech Jun 30, 2026
1e7450e
cleanup debugging code
joergbrech Jun 30, 2026
80d84d4
code cleanup
joergbrech Jun 30, 2026
ca55765
revert whitespace
joergbrech Jun 30, 2026
db16716
fix reference loft for segments
joergbrech Jun 30, 2026
4632028
adapt unit test to untrimmed loft
joergbrech Jun 30, 2026
ffb54fb
fix untrimmed fuselage face traits
joergbrech Jun 30, 2026
9b9bab9
support trimmed/untrimmed in CCPACSVessel
joergbrech Jun 30, 2026
d3fa982
support timmed/untrimmed disambiguation for pylons
joergbrech Jun 30, 2026
91d5c63
add virtual GetTrimmedLoft to CTiglAbstractGeometricComponent
joergbrech Jun 30, 2026
718f040
use trimmed geometry for visualization in TiGLCreator
joergbrech Jun 30, 2026
62966e5
small fixes
joergbrech Jun 30, 2026
3ca9f54
Merge branch 'main' into untrimmed-loft
joergbrech Jun 30, 2026
fcc690f
Merge branch 'main' into untrimmed-loft
joergbrech Jun 30, 2026
b7feaab
Merge branch 'main' into untrimmed-loft
joergbrech Jun 30, 2026
52a787e
Merge branch 'main' into untrimmed-loft
joergbrech Jun 30, 2026
f1f15af
Merge branch 'main' into untrimmed-loft
joergbrech Jul 1, 2026
1fb6733
Merge branch 'main' into untrimmed-loft
joergbrech Jul 1, 2026
540ed08
Merge branch 'main' into untrimmed-loft
joergbrech Jul 1, 2026
e4d4316
Merge branch 'main' into untrimmed-loft
joergbrech Jul 1, 2026
607bb89
use trimmed loft in cross section calculation
joergbrech Jul 1, 2026
36914c8
use untrimmed loft for surface and volume calculations, but increase …
joergbrech Jul 1, 2026
6d8c39b
revert to untrimmed loft in tiglcreator, but adapt tesselation accura…
joergbrech Jul 1, 2026
71d3933
fix failing unit test TriangularizeShape.exportVTK_CompoundWing
joergbrech Jul 2, 2026
363cd64
fix copy-paste error
joergbrech Jul 2, 2026
85e0fc8
fix whitespace
joergbrech Jul 2, 2026
1c3a8a7
remove debugging try-catch blocks
joergbrech Jul 2, 2026
2982407
remove dead code
joergbrech Jul 2, 2026
f7bad5c
remove dead code
joergbrech Jul 2, 2026
5607246
remove unnecessary include statements
joergbrech Jul 2, 2026
56dd92f
fix commented code
joergbrech Jul 2, 2026
c6b128d
remove misleading no-op MakeSolid call (defaults to true in CTiglPatc…
joergbrech Jul 2, 2026
adf54a4
remove duplicate out-of-bounds check
joergbrech Jul 2, 2026
f0dfbc4
remove override method that does not really override
joergbrech Jul 2, 2026
8b98fe6
fix indentation
joergbrech Jul 2, 2026
889173e
fix whitespace
joergbrech Jul 2, 2026
acf5406
add missing exports
joergbrech Jul 2, 2026
130ea18
add code comments to justify the increase in test tolerance
joergbrech Jul 2, 2026
7a72a08
fix wrong face trait labeling with superellipses in ccpacsfuselage
joergbrech Jul 2, 2026
d815871
use fallback for untrimmed shape in CCPACSVessel
joergbrech Jul 2, 2026
30d2d5f
add error message in segment if parent's number of faces is not what …
joergbrech Jul 2, 2026
d2742d9
remove redundant override. Improve code comment
joergbrech Jul 2, 2026
85d560c
DRY: move common functionality FacesPerSegment to common functions
joergbrech Jul 2, 2026
aa4d990
remove duplicated code in GetTrimmedLoft/GetUntrimmedLoft
joergbrech Jul 2, 2026
8180512
remove duplicated code in GetTrimmedLoft/GetUntrimmedLoft
joergbrech Jul 2, 2026
d2810ed
remove duplicated code in GetTrimmedLoft/GetUntrimmedLoft
joergbrech Jul 2, 2026
5f8714b
remove duplicated code in GetTrimmedLoft/GetUntrimmedLoft
joergbrech Jul 2, 2026
f8a490e
consistently return GetLoft in the GetUntrimmedLoft implementations
joergbrech Jul 2, 2026
e7f58de
change default parameter
joergbrech Jul 2, 2026
aa9dc9f
fix order, code cleanup
joergbrech Jul 2, 2026
e31d2a6
fix changelog entry
joergbrech Jul 2, 2026
b135f22
Remove offset workaround for TED (seems unnecessary for untrimmed lof…
joergbrech Jul 2, 2026
98cdb90
add unit tests for trimmed/untrimmed disambiguation #1262
joergbrech Jul 2, 2026
f6cac5d
tweak slider values some more
joergbrech Jul 3, 2026
e67f0ad
Merge branch 'main' into untrimmed-loft
joergbrech Jul 22, 2026
238a1c7
Merge branch 'main' into untrimmed-loft
joergbrech Jul 28, 2026
1764d72
Merge branch 'main' into untrimmed-loft
joergbrech Aug 24, 2026
66a6f84
Replace wingCleanShape with wingCleanShapeUntrimmed
joergbrech Aug 24, 2026
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
8 changes: 8 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

## [Unreleased]

### Breaking Changes
- `CTiglAbstractGeometricComponent::GetLoft()` now returns untrimmed loft by default. Use `GetTrimmedLoft()` for the previous behavior (with UV cutting at profile positions). This affects `CCPACSWing` and `CCPACSFuselage` (and their segments). [#1262](https://github.com/DLR-SC/tigl/issues/1262)

### Features
- Add optional UV profile cutting to `CTiglMakeLoft` via `setEnableProfileCutting(bool)`. When enabled, creates seams at each profile wire position for consistent UV parameterization. Wing and fuselage builders now build both trimmed and untrimmed lofts on demand. [#1262](https://github.com/DLR-SC/tigl/issues/1262)

Changes since last release
----------------
2025/09/26
Expand Down
3 changes: 3 additions & 0 deletions TIGLCreator/src/TIGLCreatorContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ Handle(AIS_Shape) TIGLCreatorContext::displayShape(const TopoDS_Shape& loft, boo
myContext->SetTransparency(shape, transparency, Standard_False);
myContext->SetDisplayMode(shape, shaded, Standard_False);
shape->SetOwnDeviationCoefficient(settings.tesselationAccuracy());
shape->SetOwnDeviationAngle(settings.tesselationDeviationAngle());

#if OCC_VERSION_HEX >= VERSION_HEX_CODE(6,7,0)
if (!myShader.IsNull()) {
Expand Down Expand Up @@ -433,6 +434,7 @@ Handle(AIS_Shape) TIGLCreatorContext::displayShape(const PNamedShape& pshape, bo
myContext->SetTransparency(shape, transparency, Standard_False);
myContext->SetDisplayMode(shape, shaded, Standard_False);
shape->SetOwnDeviationCoefficient(settings.tesselationAccuracy());
shape->SetOwnDeviationAngle(settings.tesselationDeviationAngle());

#if OCC_VERSION_HEX >= VERSION_HEX_CODE(6,7,0)
if (!myShader.IsNull()) {
Expand Down Expand Up @@ -683,6 +685,7 @@ Handle(AIS_InteractiveObject) TIGLCreatorContext::displayShapeHLMode(const TopoD
myContext->SetTransparency(shape, transparency, Standard_False);
myContext->SetWidth(shape, 3, Standard_False);
shape->SetOwnDeviationCoefficient(settings.tesselationAccuracy());
shape->SetOwnDeviationAngle(settings.tesselationDeviationAngle());

#if OCC_VERSION_HEX >= VERSION_HEX_CODE(6, 7, 0)
if (!myShader.IsNull()) {
Expand Down
2 changes: 1 addition & 1 deletion TIGLCreator/src/TIGLCreatorDocument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1752,7 +1752,7 @@ void TIGLCreatorDocument::drawAllFuselagesAndWingsSurfacePoints()
for (int fuselageIndex = 1; fuselageIndex <= GetConfiguration().GetFuselageCount(); fuselageIndex++) {
auto& fuselage = GetConfiguration().GetFuselage(fuselageIndex);

app->getScene()->displayShape(fuselage.GetLoft(), true, getDefaultShapeColor());
app->getScene()->displayShape(fuselage.GetLoft(), true, getDefaultShapeColor());

for (int segmentIndex = 1; segmentIndex <= fuselage.GetSegmentCount(); segmentIndex++) {
// Draw some points on the fuselage segment
Expand Down
20 changes: 19 additions & 1 deletion TIGLCreator/src/TIGLCreatorSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@

#include <iostream>
#include <algorithm>
#include <cmath>
#include "TIGLCreatorMaterials.h"
#include <QCoreApplication>
#include <qglobal.h>
#include "TIGLCreatorSettings.h"

const double DEFAULT_TESSELATION_ACCURACY = 0.000316;
const double DEFAULT_TESSELATION_ACCURACY = 0.004;
const double DEFAULT_TRIANGULATION_ACCURACY = 0.00070;
const QColor DEFAULT_BGCOLOR(169,237,255);
const QColor DEFAULT_SHAPE_COLOR(0, 170 ,255, 255);
Expand Down Expand Up @@ -114,6 +115,23 @@ void TIGLCreatorSettings::setDefaultMaterial(const QString& material)
_defaultMaterial = tiglMaterials::materialMap[material];
}

double TIGLCreatorSettings::tesselationDeviationAngle() const
{
// Map current tesselation accuracy (linear) to an angular deviation using the same logarithmic mapping
// NOTE: intentionally decoupled from the dialog slider endpoints. The angle
// is a pure function of the accuracy value, so keeping these fixed means a
// given accuracy always yields the same angle regardless of slider remapping.
const double WORST_TESSELATION = 0.01;
const double BEST_TESSELATION = 0.000002;
const double WORST_ANGLE = 0.15; // rad (~8.6°) coarse
const double BEST_ANGLE = 0.012; // rad (~0.69°) fine
double t = std::log(WORST_TESSELATION / _tesselationAccuracy) /
std::log(WORST_TESSELATION / BEST_TESSELATION);
t = std::max(0.0, std::min(1.0, t));
// Interpolate angle in log-space
return WORST_ANGLE * std::pow(BEST_ANGLE / WORST_ANGLE, t);
}

double TIGLCreatorSettings::tesselationAccuracy() const
{
return _tesselationAccuracy;
Expand Down
1 change: 1 addition & 0 deletions TIGLCreator/src/TIGLCreatorSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class TIGLCreatorSettings : public QObject
void setTriangulationAccuracy(double);

double tesselationAccuracy() const;
double tesselationDeviationAngle() const;
double triangulationAccuracy() const;

void setBGColor(const QColor&);
Expand Down
10 changes: 6 additions & 4 deletions TIGLCreator/src/TIGLCreatorSettingsDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
#include "TIGLCreatorSettingsDialog.h"


#define WORST_TESSELATION 0.01
#define BEST_TESSELATION 0.00001
#define WORST_TESSELATION 0.05
#define BEST_TESSELATION 0.0005

#define WORST_TRIANGULATION 0.01
#define BEST_TRIANGULATION 0.00005
Expand Down Expand Up @@ -157,7 +157,8 @@ void TIGLCreatorSettingsDialog::updateEntries()
double mu = log(dmax/dmin)/double(imax-imin);
double c = dmax / exp(-mu * (double)imin);

int tessVal = int (log(c/_settings.tesselationAccuracy())/mu);
int tessVal = static_cast<int>(std::lround(log(c/_settings.tesselationAccuracy())/mu));
tessVal = std::clamp(tessVal, sliderTesselationAccuracy->minimum(), sliderTesselationAccuracy->maximum());
sliderTesselationAccuracy->setValue(tessVal);

dmax = WORST_TRIANGULATION, dmin = BEST_TRIANGULATION;
Expand All @@ -166,7 +167,8 @@ void TIGLCreatorSettingsDialog::updateEntries()
mu = log(dmax/dmin)/double(imax-imin);
c = dmax / exp(-mu * (double)imin);

int triaVal = int (log(c/_settings.triangulationAccuracy())/mu);
int triaVal = static_cast<int>(std::lround(log(c/_settings.triangulationAccuracy())/mu));
triaVal = std::clamp(triaVal, sliderTriangulationAccuracy->minimum(), sliderTriangulationAccuracy->maximum());
sliderTriangulationAccuracy->setValue(triaVal);

_bgcolor = _settings.BGColor();
Expand Down
12 changes: 12 additions & 0 deletions src/common/tiglcommonfunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ unsigned int GetNumberOfFaces(const TopoDS_Shape& shape)
return iFaces;
}

int FacesPerSegment(int nFaces, int nSegments)
{
if (nSegments <= 0) {
return 1;
}
int facesPerSegment = (nFaces + nSegments - 1) / nSegments;
if (facesPerSegment < 1) {
facesPerSegment = 1;
}
return facesPerSegment;
}

unsigned int GetNumberOfSubshapes(const TopoDS_Shape &shape)
{
if (shape.ShapeType() == TopAbs_COMPOUND) {
Expand Down
5 changes: 5 additions & 0 deletions src/common/tiglcommonfunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ TIGL_EXPORT unsigned int GetNumberOfEdges(const TopoDS_Shape& shape);
// returns the number of faces of the current shape
TIGL_EXPORT unsigned int GetNumberOfFaces(const TopoDS_Shape& shape);

// Distributes nFaces faces evenly over nSegments segments, rounding up so that
// every face is covered (ceil division). The result is clamped to a minimum of 1.
// Used to determine the number of loft faces per profile segment.
TIGL_EXPORT int FacesPerSegment(int nFaces, int nSegments);

TIGL_EXPORT TopoDS_Edge GetEdge(const TopoDS_Shape& shape, int iEdge);

TIGL_EXPORT TopoDS_Face GetFace(const TopoDS_Shape& shape, int iFace);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
#include "Debugging.h"

#include <BRepOffsetAPI_ThruSections.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepBuilderAPI_Transform.hxx>

namespace tigl
{
Expand All @@ -47,31 +45,7 @@ PNamedShape CCPACSControlSurfaceOuterShapeLeadingEdge::GetLoft(PNamedShape wingC
assert(shapeBox);

// perform the boolean intersection of the flap box with the wing

// Workaround for OpenCASCADE boolean intersection issues on LEDs:
// OCC seems to fail to create correct side faces when LEDs and the wing intersect
// along tangential or very thin regions (typical for LEDs).
// TED intersections seem to work fine.
//
// Solution: apply a small inward offset (epsilon) to the device box
// before performing the boolean intersection.

gp_Vec epsilonVec = upDir;
epsilonVec *= -1e-6;
gp_Trsf trsf;
trsf.SetTranslation(epsilonVec);

BRepBuilderAPI_Transform transformer(shapeBox->Shape(), trsf, true);
TopoDS_Shape offsetBox = transformer.Shape();

PNamedShape shapeBoxOffset(new CNamedShape(offsetBox, shapeBox->Name().c_str()));


BRepAlgoAPI_Common common(wingCleanShape->Shape(), shapeBoxOffset->Shape());;
common.Build();

TopoDS_Shape outerShapeTopo = common.Shape();
PNamedShape outerShape(new CNamedShape(outerShapeTopo, shapeBox->Name().c_str()));
PNamedShape outerShape = CBopCommon(wingCleanShape, shapeBox);

if (NeedsWingIntersection()) {
return ControlSurfaceDeviceHelper::outerShapeGetLoft(shapeBox, outerShape, _uid);
Expand Down
69 changes: 52 additions & 17 deletions src/ducts/CCPACSDuct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ namespace tigl {
CCPACSDuct::CCPACSDuct(CCPACSDucts* parent, CTiglUIDManager* uidMgr)
: generated::CPACSDuct(parent, uidMgr)
, CTiglRelativelyPositionedComponent(static_cast<std::string*>(nullptr), &m_transformation, &m_symmetry)
, loftUntrimmed(*this, &CCPACSDuct::BuildLoftUntrimmed)
, loftTrimmed(*this, &CCPACSDuct::BuildLoftTrimmed)
{}

CCPACSConfiguration& CCPACSDuct::GetConfiguration() const
Expand All @@ -55,31 +57,48 @@ TiglGeometricComponentIntent CCPACSDuct::GetComponentIntent() const
}

PNamedShape CCPACSDuct::BuildLoft() const
{
return *loftUntrimmed;
}

void CCPACSDuct::BuildLoftUntrimmed(PNamedShape& cache) const
{
BuildLoftImpl(cache, false);
}

void CCPACSDuct::BuildLoftTrimmed(PNamedShape& cache) const
{
BuildLoftImpl(cache, true);
}

void CCPACSDuct::BuildLoftImpl(PNamedShape& cache, bool trim) const
{
TiglContinuity cont = m_segments.GetSegment(1).GetContinuity();
Standard_Boolean smooth = (cont == ::C0? false : true);

CTiglMakeLoft lofter;
// add profiles
lofter.setMakeSolid(true);
lofter.setMakeSmooth(smooth);
// Only the trimmed loft is cut at the profiles; the untrimmed loft is a
// single continuous surface.
lofter.setEnableProfileCutting(trim);

for (int i=1; i <= m_segments.GetSegmentCount(); i++) {
lofter.addProfiles(m_segments.GetSegment(i).GetStartWire());
}
lofter.addProfiles(m_segments.GetSegment(m_segments.GetSegmentCount()).GetEndWire());

// add guides
lofter.addGuides(m_segments.GetGuideCurveWires());

lofter.setMakeSolid(true);
lofter.setMakeSmooth(smooth);

TopoDS_Shape loftShape = lofter.Shape();
TopoDS_Shape loftShape = lofter.Shape();

std::string loftName = GetUID();
std::string loftShortName = GetShortShapeName();
PNamedShape loft(new CNamedShape(loftShape, loftName.c_str(), loftShortName.c_str()));
SetFaceTraits(loft);
cache = std::make_shared<CNamedShape>(loftShape, loftName.c_str(), loftShortName.c_str());

return loft;
// The trimmed loft has one face group per segment, whereas the untrimmed
// loft's aerodynamic faces form a single continuous group.
SetFaceTraits(cache, trim ? m_segments.GetSegmentCount() : 1);
}

// get short name for loft
Expand All @@ -100,8 +119,12 @@ std::string CCPACSDuct::GetShortShapeName() const
return "UNKNOWN";
}

void CCPACSDuct::SetFaceTraits (PNamedShape loft) const
void CCPACSDuct::SetFaceTraits(PNamedShape loft, int nSegments) const
{
// Face layout: [aerodynamic faces][optional symmetry faces][front/rear caps].
// For the trimmed loft the aerodynamic (and symmetry) faces are grouped per
// segment (nSegments > 1); for the untrimmed loft they form a single group
// (nSegments == 1).
int nFacesTotal = GetNumberOfFaces(loft->Shape());
int nFacesAero = nFacesTotal;
bool hasSymmetryPlane = GetNumberOfEdges(m_segments.GetSegment(1).GetEndWire()) > 1;
Expand All @@ -119,10 +142,7 @@ void CCPACSDuct::SetFaceTraits (PNamedShape loft) const
nFacesAero-=1;
}

// if we have a smooth surface, the whole fuslage is treatet as one segment
int nSegments = m_segments.GetSegmentCount();

int facesPerSegment = nFacesAero/ nSegments;
int facesPerSegment = FacesPerSegment(nFacesAero, nSegments);

int iFaceTotal = 0;
int nSymmetryFaces = (int) hasSymmetryPlane;
Expand All @@ -135,19 +155,34 @@ void CCPACSDuct::SetFaceTraits (PNamedShape loft) const
}
}

// set the caps
int iFace = 2;
for (;iFaceTotal < nFacesTotal; ++iFaceTotal) {
loft->FaceTraits(iFaceTotal).SetName(names[iFace++].c_str());
for (;iFaceTotal < nFacesTotal; ++iFaceTotal, ++iFace) {
if (iFace < (int)names.size()) {
loft->FaceTraits(iFaceTotal).SetName(names[iFace].c_str());
}
}
}

PNamedShape CCPACSDuct::GetUntrimmedLoft() const
{
// GetLoft() returns the untrimmed loft, so this is the untrimmed loft by
// definition.
return GetLoft();
}

PNamedShape CCPACSDuct::GetTrimmedLoft() const
{
return *loftTrimmed;
}

void CCPACSDuct::RegisterInvalidationCallback(std::function<void()> const& fn){
invalidationCallbacks.push_back(fn);
}

void CCPACSDuct::InvalidateImpl(const boost::optional<std::string>&) const
{
loftTrimmed.clear();
loftUntrimmed.clear();
CTiglAbstractGeometricComponent::Reset();
for (auto const& invalidator: invalidationCallbacks) {
invalidator();
Expand Down
13 changes: 12 additions & 1 deletion src/ducts/CCPACSDuct.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class CCPACSDuct : public generated::CPACSDuct, public CTiglRelativelyPositioned
//as a callback.
TIGL_EXPORT void RegisterInvalidationCallback(std::function<void()> const&);

TIGL_EXPORT PNamedShape GetTrimmedLoft() const override;
TIGL_EXPORT PNamedShape GetUntrimmedLoft() const;

protected:
PNamedShape BuildLoft() const override;

Expand All @@ -55,10 +58,18 @@ class CCPACSDuct : public generated::CPACSDuct, public CTiglRelativelyPositioned
// get short name for loft
std::string GetShortShapeName() const;

void SetFaceTraits (PNamedShape loft) const;
// Names the loft's faces. nSegments controls whether the aerodynamic faces
// are grouped per segment (trimmed loft) or as a single group (untrimmed loft).
void SetFaceTraits (PNamedShape loft, int nSegments) const;

std::vector<std::function<void()>> invalidationCallbacks;

mutable Cache<PNamedShape, CCPACSDuct> loftUntrimmed; /**< Duct surface, untrimmed (without UV cuts at profiles) */
mutable Cache<PNamedShape, CCPACSDuct> loftTrimmed; /**< Duct surface, trimmed (with UV cuts at profiles) */

void BuildLoftTrimmed(PNamedShape& cache) const;
void BuildLoftUntrimmed(PNamedShape& cache) const;
void BuildLoftImpl(PNamedShape& cache, bool trim) const;
};

}
Loading
Loading