Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const VariablesPanel = ({
(node: DebugTreeNode) => {
if (!isDebuggerVisible || node.isComplex) return false
if (node.debugIndex !== undefined) return true
return debugVariableIndexes?.has(node.fullPath) ?? debugVariableIndexes?.has(node.compositeKey) ?? false
return debugVariableIndexes?.has(node.fullPath) || debugVariableIndexes?.has(node.compositeKey) || false
},
[isDebuggerVisible, debugVariableIndexes],
)
Expand Down
55 changes: 55 additions & 0 deletions src/renderer/screens/workspace-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,61 @@ const WorkspaceScreen = () => {
}
})

// Process top-level user-data-type variables (structs and any unresolved FBs)
const userDataTypeVars = pou.data.variables.filter((variable) => variable.type.definition === 'user-data-type')
userDataTypeVars.forEach((udtVar) => {
const typeNameUpper = udtVar.type.value.toUpperCase()

const isStandardFB = StandardFunctionBlocks.pous.some(
(fb: { name: string; type: string }) =>
fb.name.toUpperCase() === typeNameUpper && fb.type.toLowerCase().replace(/[-_]/g, '') === 'functionblock',
)
const isCustomFB = project.data.pous.some(
(p) => p.type === 'function-block' && p.data.name.toUpperCase() === typeNameUpper,
)

let variablesToProcess:
| Array<{ name: string; class: string; type: { definition: string; value: string } }>
| undefined

if (isStandardFB || isCustomFB) {
const standardFB = StandardFunctionBlocks.pous.find(
(fb: { name: string }) => fb.name.toUpperCase() === typeNameUpper,
)
if (standardFB) {
variablesToProcess = ensureEnoVariable(standardFB.variables)
} else {
const customFB = project.data.pous.find(
(p) => p.type === 'function-block' && p.data.name.toUpperCase() === typeNameUpper,
)
if (customFB && customFB.type === 'function-block') {
variablesToProcess = ensureEnoVariable(
customFB.data.variables as Array<{
name: string
class: string
type: { definition: string; value: string }
}>,
)
}
}
} else {
const structType = project.data.dataTypes.find((dt) => dt.name.toUpperCase() === typeNameUpper)
if (structType && structType.derivation === 'structure') {
variablesToProcess = structType.variable.map((field) => ({
name: field.name,
class: 'local' as const,
type: { definition: field.type.definition, value: field.type.value },
}))
}
}

if (variablesToProcess) {
const debugPathPrefix = buildDebugPath(programInstance.name, udtVar.name)
const variableNamePrefix = udtVar.name
processNestedVariables(variablesToProcess, pou.data.name, debugPathPrefix, variableNamePrefix)
}
})

if (pou.data.body.language === 'ld') {
const currentLadderFlow = ladderFlows.find((flow) => flow.name === pou.data.name)
if (currentLadderFlow) {
Expand Down
46 changes: 31 additions & 15 deletions src/renderer/store/slices/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1220,26 +1220,42 @@ export const createSharedSlice: StateCreator<

pous.map((pou) => pou.type !== 'program' && getState().libraryActions.addLibrary(pou.data.name, pou.type))

const graphicalPous = [...ladderPous, ...fbdPous]
if (graphicalPous.length) {
const state = getState()
// Reclassify ALL POUs' variables with full context.
// The text parser can't determine type definitions accurately since it doesn't have
// the full project context. Re-parse with pous, dataTypes, and libraries to correctly
// classify FB instances as 'derived' vs structs as 'user-data-type'.
{
const reclassState = getState()
const {
project: {
data: { dataTypes },
data: { dataTypes: reclassDataTypes },
},
libraries,
} = state

graphicalPous.forEach((pou) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
const iecString = generateIecVariablesToString(pou.data.variables as any)
const reparsedVariables: PLCVariable[] = parseIecStringToVariables(iecString, pous, dataTypes, libraries)
getState().projectActions.setPouVariables({
pouName: pou.data.name,
variables: reparsedVariables,
})
libraries: reclassLibraries,
} = reclassState

pous.forEach((pou) => {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
const iecString = generateIecVariablesToString(pou.data.variables as any)
const reparsedVariables: PLCVariable[] = parseIecStringToVariables(
iecString,
pous,
reclassDataTypes,
reclassLibraries,
)
getState().projectActions.setPouVariables({
pouName: pou.data.name,
variables: reparsedVariables,
})
} catch (err) {
console.error(`[Reclassify] Failed to reclassify variables for POU "${pou.data.name}":`, err)
}
})
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Sync graphical POU nodes with reclassified variables
const graphicalPous = [...ladderPous, ...fbdPous]
if (graphicalPous.length) {
const freshState = getState()
const freshLadderFlows = freshState.ladderFlows
const freshFBDFlows = freshState.fbdFlows
Expand Down
30 changes: 25 additions & 5 deletions src/utils/debug-tree-traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,22 +208,32 @@ function traverseNestedNode<T>(
const children: T[] = []

for (const field of structVariables) {
// Structure fields use .value. prefix
const fieldFullPath = `${fullPath}.value.${field.name.toUpperCase()}`
const fieldCompositeKey = `${compositeKey}.${field.name}`

if (field.type.definition === 'base-type') {
const debugVar = findDebugVariable(debugVariables, fieldFullPath)
// Use fallback to try both struct-style (.value.) and FB-style paths.
// The xml2st compiler converts structs to FBs, so debug.c may use either path style.
const result = findDebugVariableForField(debugVariables, fullPath, field.name)
children.push(
visitor.visitLeaf(
field.name,
fieldFullPath,
result.matchedPath,
fieldCompositeKey,
field.type.value.toUpperCase(),
debugVar?.index,
result.match?.index,
),
)
} else if (field.type.definition === 'user-data-type') {
// Try FB-style path first (compiler may have converted struct to FB)
const fbStylePath = `${fullPath}.${field.name.toUpperCase()}`
const structStylePath = `${fullPath}.value.${field.name.toUpperCase()}`
const hasFbMatch = debugVariables.some(
(dv) =>
dv.name.toUpperCase().startsWith(fbStylePath.toUpperCase() + '.') ||
dv.name.toUpperCase() === fbStylePath.toUpperCase(),
)
const fieldFullPath = hasFbMatch ? fbStylePath : structStylePath

const childTypeDef = isFunctionBlock(field.type.value, projectPous) ? 'derived' : 'user-data-type'
children.push(
traverseNestedNode(
Expand All @@ -237,6 +247,16 @@ function traverseNestedNode<T>(
),
)
} else if (field.type.definition === 'array' && field.type.data) {
// Use fallback path for array fields too
const fbStylePath = `${fullPath}.${field.name.toUpperCase()}`
const structStylePath = `${fullPath}.value.${field.name.toUpperCase()}`
const hasFbMatch = debugVariables.some(
(dv) =>
dv.name.toUpperCase().startsWith(fbStylePath.toUpperCase() + '.') ||
dv.name.toUpperCase() === fbStylePath.toUpperCase(),
)
const fieldFullPath = hasFbMatch ? fbStylePath : structStylePath

children.push(
traverseNestedNode(
field.name,
Expand Down