From 26a9d26061b19910e8080d6f97e3a1f1d8009fd9 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Wed, 19 Nov 2025 12:22:59 +0100 Subject: [PATCH] SoBrepEdgeSet : Fix color override crash --- src/Mod/Part/Gui/SoBrepEdgeSet.cpp | 138 +++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 35 deletions(-) diff --git a/src/Mod/Part/Gui/SoBrepEdgeSet.cpp b/src/Mod/Part/Gui/SoBrepEdgeSet.cpp index 4c0b11e754..5100d8ffb1 100644 --- a/src/Mod/Part/Gui/SoBrepEdgeSet.cpp +++ b/src/Mod/Part/Gui/SoBrepEdgeSet.cpp @@ -130,6 +130,17 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction* action) bool hasColorOverride = (ctx2 && !ctx2->colors.empty()); if (hasColorOverride) { + // Special handling for edge color overrides (e.g. highlighting specific edges). + // We initially attempted to use the same logic as SoBrepFaceSet (setting + // SoMaterialBindingElement::PER_PART_INDEXED and populating SoLazyElement arrays). + // However, this proved brittle for SoIndexedLineSet, causing persistent crashes + // in SoMaterialBundle/SoGLLazyElement (SIGSEGV) due to internal Coin3D state + // mismatches when mixing Lit (default) and Unlit (highlighted) states. + // + // To ensure stability, we bypass the base class GLRender entirely and perform + // a manual dual-pass render using direct OpenGL calls: + // Pass 1: Render default lines using the current Coin3D state (Lighting enabled). + // Pass 2: Render highlighted lines with Lighting disabled to ensure bright, flat colors. state->push(); const SoCoordinateElement* coords; @@ -140,6 +151,8 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction* action) const int32_t* mindices; int numcindices; SbBool normalCacheUsed; + + // We request normals (true) because default lines need them for lighting this->getVertexData( state, coords, @@ -149,54 +162,109 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction* action) tindices, mindices, numcindices, - false, + true, normalCacheUsed ); + const SbVec3f* coords3d = coords->getArrayPtr3(); + + // Apply the default material settings (Standard Lighting/Material) + // This ensures default lines look correct (e.g. Black) SoMaterialBundle mb(action); mb.sendFirst(); - std::vector packedColors; - std::vector matIdx; - bool hasTransparency = false; - - // CORRECTED way to get default packed color - const SoLazyElement* lazyElem = SoLazyElement::getInstance(state); - packedColors.push_back( - lazyElem->getDiffuse(state, 0).getPackedValue(lazyElem->getTransparency(state, 0)) - ); + // We will collect highlighted segments to render them in a second pass + // so we don't have to switch GL state constantly. + struct HighlightSegment + { + int startIndex; + Base::Color color; + }; + std::vector highlights; int linecount = 0; - for (int i = 0; i < numcindices; i++) { - if (cindices[i] < 0) { - auto it = ctx2->colors.find(linecount); - if (it != ctx2->colors.end()) { - packedColors.push_back(ctx2->packColor(it->second, hasTransparency)); - matIdx.push_back(packedColors.size() - 1); - } - else { - auto it_all = ctx2->colors.find(-1); - if (it_all != ctx2->colors.end()) { - packedColors.push_back(ctx2->packColor(it_all->second, hasTransparency)); - matIdx.push_back(packedColors.size() - 1); - } - else { - matIdx.push_back(0); - } - } - linecount++; + int i = 0; + + // --- PASS 1: Render Default Lines (Lit) --- + while (i < numcindices) { + int startIndex = i; + + // Check if this line index has an override color + const Base::Color* pColor = nullptr; + auto it = ctx2->colors.find(linecount); + if (it != ctx2->colors.end()) { + pColor = &it->second; } + else { + // Check for wildcard color + auto it_all = ctx2->colors.find(-1); + if (it_all != ctx2->colors.end()) { + pColor = &it_all->second; + } + } + + if (pColor) { + // This is a highlighted line. Save it for Pass 2. + highlights.push_back({startIndex, *pColor}); + + // Skip over the indices for this line + while (i < numcindices && cindices[i] >= 0) { + i++; + } + i++; // skip the -1 separator + } + else { + // This is a default line. Render immediately with current (Lit) state. + glBegin(GL_LINE_STRIP); + while (i < numcindices) { + int32_t idx = cindices[i++]; + if (idx < 0) { + break; + } + + if (idx < coords->getNum()) { + if (normals) { + glNormal3fv((const GLfloat*)(normals + idx)); + } + glVertex3fv((const GLfloat*)(coords3d + idx)); + } + } + glEnd(); + } + linecount++; } - if (!matIdx.empty()) { - SoLazyElement::setPacked(state, this, packedColors.size(), packedColors.data(), hasTransparency); - SoMaterialBindingElement::set(state, this, SoMaterialBindingElement::PER_PART_INDEXED); - this->materialIndex.setValues(0, matIdx.size(), matIdx.data()); + // --- PASS 2: Render Highlighted Lines (Unlit) --- + if (!highlights.empty()) { + // Disable lighting and textures so the color is flat and bright + glPushAttrib(GL_LIGHTING_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT); + glDisable(GL_LIGHTING); + glDisable(GL_TEXTURE_2D); + + for (const auto& segment : highlights) { + // Apply the explicit color from the map + // Note: FreeCAD Base::Color transparency is 0.0 (opaque) to 1.0 (transparent) + // OpenGL Alpha is 1.0 (opaque) to 0.0 (transparent) + glColor4f(segment.color.r, segment.color.g, segment.color.b, 1.0f - segment.color.a); + + glBegin(GL_LINE_STRIP); + int j = segment.startIndex; + while (j < numcindices) { + int32_t idx = cindices[j++]; + if (idx < 0) { + break; + } + + if (idx < coords->getNum()) { + glVertex3fv((const GLfloat*)(coords3d + idx)); + } + } + glEnd(); + } + glPopAttrib(); } - inherited::GLRender(action); - - this->materialIndex.deleteValues(0); + // Do NOT call inherited::GLRender(action). We have handled all rendering manually. state->pop(); return; }