Skip to content
Draft
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
5 changes: 4 additions & 1 deletion rust/kcl-lib/src/std/appearance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
let color: String = args.get_kw_arg("color", &RuntimeType::string(), exec_state)?;
let metalness: Option<TyF64> = args.get_kw_arg_opt("metalness", &RuntimeType::count(), exec_state)?;
let roughness: Option<TyF64> = args.get_kw_arg_opt("roughness", &RuntimeType::count(), exec_state)?;
let opacity: Option<TyF64> = args.get_kw_arg_opt("opacity", &RuntimeType::count(), exec_state)?;

// Make sure the color if set is valid.
if !HEX_REGEX.is_match(&color) {
Expand All @@ -76,6 +77,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
color,
metalness.map(|t| t.n),
roughness.map(|t| t.n),
opacity.map(|t| t.n),
exec_state,
args,
)
Expand All @@ -88,6 +90,7 @@ async fn inner_appearance(
color: String,
metalness: Option<f64>,
roughness: Option<f64>,
opacity: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<SolidOrImportedGeometry, KclError> {
Expand All @@ -106,7 +109,7 @@ async fn inner_appearance(
r: rgb.red,
g: rgb.green,
b: rgb.blue,
a: 100.0,
a: opacity.unwrap_or(100.0) as f32,
Copy link
Contributor

@lee-at-zoo-corp lee-at-zoo-corp Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happened to be lucky and catch Mike's comment on alpha being normalized to [0,1] - this value should probably be 1.0

};

exec_state
Expand Down
2 changes: 2 additions & 0 deletions rust/kcl-lib/std/solid.kcl
Original file line number Diff line number Diff line change
Expand Up @@ -1290,4 +1290,6 @@ export fn appearance(
metalness?: number(Count),
/// Roughness of the new material, a percentage like 95.7.
roughness?: number(Count),
/// Opacity. Defaults to 100 (totally opaque). 0 would be totally transparent.
opacity?: number(Count),
): [Solid; 1+] | ImportedGeometry {}
72 changes: 72 additions & 0 deletions src/lang/modifyAst/transforms.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,35 @@ appearance(
)`)
})

it('should add a call with opacity', async () => {
const {
artifactGraph,
ast,
sketches: objects,
} = await getAstAndSketchSelections(
box,
instanceInThisFile,
kclManagerInThisFile
)
const result = addAppearance({
ast,
artifactGraph,
objects,
color: '#FF0000',
opacity: await getKclCommandValue(
'0.5',
instanceInThisFile,
rustContextInThisFile
),
wasmInstance: instanceInThisFile,
})
if (err(result)) throw result
await runNewAstAndCheckForSweep(result.modifiedAst, rustContextInThisFile)
const newCode = recast(result.modifiedAst, instanceInThisFile)
expect(newCode).toContain(`${box}
appearance(extrude001, color = "#FF0000", opacity = 0.5)`)
})

async function runEditAppearanceTest(
code: string,
nodeToEdit: PathToNode,
Expand Down Expand Up @@ -812,6 +841,49 @@ appearance(extrude001, color = "#00FF00")`
expect(newCode).toContain(expectedNewCode)
})

it('should edit a call to add opacity', async () => {
const code = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
extrude001 = extrude(profile001, length = 1)
appearance(extrude001, color = "#FF0000")`
const expectedNewCode = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
extrude001 = extrude(profile001, length = 1)
appearance(extrude001, color = "#FF0000", opacity = 0.75)`
const nodeToEdit: PathToNode = [
['body', ''],
[3, 'index'],
['expression', 'ExpressionStatement'],
]

const {
artifactGraph,
ast,
sketches: objects,
} = await getAstAndSketchSelections(
code,
instanceInThisFile,
kclManagerInThisFile
)
const result = addAppearance({
ast,
artifactGraph,
objects,
color: '#FF0000',
opacity: await getKclCommandValue(
'0.75',
instanceInThisFile,
rustContextInThisFile
),
nodeToEdit,
wasmInstance: instanceInThisFile,
})
if (err(result)) throw result
await runNewAstAndCheckForSweep(result.modifiedAst, rustContextInThisFile)
const newCode = recast(result.modifiedAst, instanceInThisFile)
expect(newCode).toContain(expectedNewCode)
})

// TODO: missing multi-objects test
})
})
10 changes: 10 additions & 0 deletions src/lang/modifyAst/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export function addAppearance({
wasmInstance,
metalness,
roughness,
opacity,
nodeToEdit,
}: {
ast: Node<Program>
Expand All @@ -361,6 +362,7 @@ export function addAppearance({
wasmInstance: ModuleType
metalness?: KclCommandValue
roughness?: KclCommandValue
opacity?: KclCommandValue
nodeToEdit?: PathToNode
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
// 1. Clone the ast and nodeToEdit so we can freely edit them
Expand Down Expand Up @@ -391,11 +393,15 @@ export function addAppearance({
const roughnessExpr = roughness
? [createLabeledArg('roughness', valueOrVariable(roughness))]
: []
const opacityExpr = opacity
? [createLabeledArg('opacity', valueOrVariable(opacity))]
: []
const objectsExpr = createVariableExpressionsArray(vars.exprs)
const call = createCallExpressionStdLibKw('appearance', objectsExpr, [
...colorExpr,
...metalnessExpr,
...roughnessExpr,
...opacityExpr,
])

if (metalness && 'variableName' in metalness && metalness.variableName) {
Expand All @@ -406,6 +412,10 @@ export function addAppearance({
insertVariableAndOffsetPathToNode(roughness, modifiedAst, mNodeToEdit)
}

if (opacity && 'variableName' in opacity && opacity.variableName) {
insertVariableAndOffsetPathToNode(opacity, modifiedAst, mNodeToEdit)
}

// 3. If edit, we assign the new function call declaration to the existing node,
// otherwise just push to the end
const pathToNode = setCallInAst({
Expand Down
5 changes: 5 additions & 0 deletions src/lib/commandBarConfigs/modelingCommandConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export type ModelingCommandSchema = {
color: string
metalness?: KclCommandValue
roughness?: KclCommandValue
opacity?: KclCommandValue
}
Translate: {
nodeToEdit?: PathToNode
Expand Down Expand Up @@ -1576,6 +1577,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
inputType: 'kcl',
required: false,
},
opacity: {
inputType: 'kcl',
required: false,
},
},
},
Translate: {
Expand Down
15 changes: 15 additions & 0 deletions src/lib/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2706,6 +2706,20 @@ async function prepareToEditAppearance({
roughness = result
}

let opacity: KclCommandValue | undefined
if (operation.labeledArgs.opacity) {
const result = await stringToKclExpression(
code.slice(
...operation.labeledArgs.opacity.sourceRange.map(boundToUtf16)
),
rustContext
)
if (err(result) || 'errors' in result) {
return { reason: "Couldn't retrieve opacity argument" }
}
opacity = result
}

// 3. Assemble the default argument values for the command,
// with `nodeToEdit` set, which will let the actor know
// to edit the node that corresponds to the StdLibCall.
Expand All @@ -2714,6 +2728,7 @@ async function prepareToEditAppearance({
color,
metalness,
roughness,
opacity,
nodeToEdit: pathToNodeFromRustNodePath(operation.nodePath),
}
return {
Expand Down
Loading